const turtle = new Turtle();

const Start_Packing = 1; // min=0, max=1, step=1 (0=Tegn, 1=Pakk)
const isCircle = 0;

const path0 = `M-36,-27 C-30,-34 -19,-36 -11,-35 C-2,-34 6,-28 8,-19 C10,-11 8,-2 1,4 C-5,10 -15,12 -23,10 C-31,8 -37,1 -38,-7 C-39,-15 -35,-23 -29,-28`; // type=path Tegn form 1
const path1 = ``; // type=path Tegn form 2
const path2 = ``; // type=path Tegn form 3
const path3 = ``; // type=path Tegn form 4

const canvas_size = 95;
const grid_cols = 20;
const grid_rows = 20;
const shape_scale = 0.38;

const master_shapes = [];

function cross(a, b) { return a[0]*b[1]-a[1]*b[0]; }
function equal(a,b) { return .001>dist_sqr(a,b); }
function scale(a,b) { return [a[0]*b,a[1]*b]; }
function add(a,b) { return [a[0]+b[0],a[1]+b[1]]; }
function sub(a,b) { return [a[0]-b[0],a[1]-b[1]]; }
function dot(a,b) { return a[0]*b[0]+a[1]*b[1]; }
function dist_sqr(a,b) { return (a[0]-b[0])**2+(a[1]-b[1])**2; }
function transform(a, mat) { return [a[0]*mat[0]+a[1]*mat[2], a[0]*mat[1]+a[1]*mat[3]]; }

class Shape {
    constructor(svg) {
        this.strokes = getStrokes(svg);
        if (this.strokes.length === 0) { this.ready = false; return; }

        let allPoints = this.strokes.flat();
        if (allPoints.length < 2) { this.ready = false; return; }

        const center = scale(allPoints.reduce((a,c) => add(a,c), [0,0]), 1/allPoints.length);
        allPoints = allPoints.map(p => sub(p, center));
        let r = 0;
        allPoints.forEach(p => r = Math.max(r, Math.hypot(p[0], p[1])));
        if (r === 0) { this.ready = false; return; }

        this.strokes = this.strokes.map(stroke =>
            stroke.map(p => scale(sub(p, center), 1/r))
        );

        this.hull = this.getConvexHull(this.strokes.flat());
        this.ready = true;
    }

    getConvexHull(S) {
        S = S.filter((p, i) => i % 4 === 0);
        S = [...S].sort((a,b) => a[0] - b[0]);
        if (S.length === 0) return [];
        let pointOnHull = S[0], endpoint, i = 0;
        const P = [];
        do {
            P[i] = pointOnHull;
            endpoint = S[0];
            for (let j = 0; j < S.length; j++) {
                if (equal(endpoint, pointOnHull) || cross(sub(S[j], P[i]), sub(endpoint, P[i])) < 0) {
                    endpoint = S[j];
                }
            }
            i++;
            pointOnHull = [...endpoint];
        } while (!equal(endpoint, P[0]) && i < S.length);
        return P;
    }

    draw(center, size, turtle, polygons) {
        const rot = [1, 0, 0, 1];
        const poly = polygons.create();
        const tr = (p) => add(scale(transform(p, rot), size), center);

        poly.addPoints(...this.hull.map(p => tr(p)));

        // Hver strek tegnes separat — ingen linje mellom streker
        this.strokes.forEach(stroke => {
            for (let i = 0; i < stroke.length-1; i++) {
                poly.addSegments(tr(stroke[i]), tr(stroke[i+1]));
            }
        });

        polygons.draw(turtle, poly, true);
    }
}

// Deler opp path i separate streker ved hver M-kommando
function getStrokes(svg, count = 80) {
    const parsed = new Path(svg);
    const strokes = [];
    parsed.segments.forEach(seg => {
        if (seg.constructor.name === 'MoveTo' || seg.type === 'M') {
            strokes.push([]);
        }
        if (strokes.length === 0) strokes.push([]);
        const last = strokes[strokes.length-1];
        const len = seg.length();
        if (len === 0) {
            last.push(seg.p(0).slice(0,2));
        } else {
            const steps = Math.max(2, Math.round(count * len / 200));
            for (let i = 0; i <= steps; i++) {
                last.push(seg.p(i/steps).slice(0,2));
            }
        }
    });
    return strokes.filter(s => s.length > 1);
}

const polygons = new Polygons();
[path0, path1, path2, path3].forEach(p => {
    if (p && p.length > 10) {
        const s = new Shape(p);
        if (s.ready) master_shapes.push(s);
    }
});

function walk(i) {
    if (Start_Packing === 0 || master_shapes.length === 0) return false;
    const total = grid_cols * grid_rows;
    if (i >= total) return false;
    const col = i % grid_cols;
    const row = Math.floor(i / grid_cols);
    const cell_w = (canvas_size * 2) / grid_cols;
    const cell_h = (canvas_size * 2) / grid_rows;
    const x = -canvas_size + cell_w * col + cell_w / 2;
    const y = -canvas_size + cell_h * row + cell_h / 2;
    const size = Math.min(cell_w, cell_h) * shape_scale;
    const shape = master_shapes[Math.floor(Math.random() * master_shapes.length)];
    shape.draw([x, y], size, turtle, polygons);
    return (i + 1) < total;
}

function Path(svg) {
    class MoveTo { constructor(p) { this.p0 = p; this.type='M'; } p(t, s) { return [...this.p0, 1, 0]; } length() { return 0; } }
    class LineTo { constructor(p0, p1) { this.p0 = p0, this.p1 = p1; this.type='L'; } p(t, s = 1) { const nt = 1 - t, p0 = this.p0, p1 = this.p1; return [nt*p0[0] + t*p1[0], nt*p0[1] + t*p1[1], (p1[0] - p0[0]) * s, (p1[1] - p0[1]) * s]; } length() { return Math.hypot(this.p0[0]-this.p1[0], this.p0[1]-this.p1[1]); } }
    class BezierTo {
        constructor(p0, c0, c1, p1) { this.p0=p0; this.c0=c0; this.c1=c1; this.p1=p1; this.type='C'; }
        p(t, s=1) {
            const nt=1-t, p0=this.p0, c0=this.c0, c1=this.c1, p1=this.p1;
            return [
                nt*nt*nt*p0[0]+3*t*nt*nt*c0[0]+3*t*t*nt*c1[0]+t*t*t*p1[0],
                nt*nt*nt*p0[1]+3*t*nt*nt*c0[1]+3*t*t*nt*c1[1]+t*t*t*p1[1],
                (3*nt*nt*(c0[0]-p0[0])+6*t*nt*(c1[0]-c0[0])+3*t*t*(p1[0]-c1[0]))*s,
                (3*nt*nt*(c0[1]-p0[1])+6*t*nt*(c1[1]-c0[1])+3*t*t*(p1[1]-c1[1]))*s
            ];
        }
        length() { return this._length || (this._length = Array.from({length:25}, (x,i) => this.p(i/25)).reduce((a,c,i,v) => i>0 ? a+Math.hypot(c[0]-v[i-1][0],c[1]-v[i-1][1]) : a, 0)); }
    }
    class Path {
        constructor(svg) { this.segments = []; this.parsePath(svg); }
        parsePath(svg) {
            const t = svg.match(/([0-9.-]+|[MLC])/g);
            if (!t) return;
            for (let s, i=0; i<t.length;) {
                switch(t[i++]) {
                    case 'M': this.add(new MoveTo(s=[parseFloat(t[i++]),parseFloat(t[i++])])); break;
                    case 'L': this.add(new LineTo(s, s=[parseFloat(t[i++]),parseFloat(t[i++])])); break;
                    case 'C': this.add(new BezierTo(s, [parseFloat(t[i++]),parseFloat(t[i++])], [parseFloat(t[i++]),parseFloat(t[i++])], s=[parseFloat(t[i++]),parseFloat(t[i++])])); break;
                }
            }
        }
        add(segment) { this.segments.push(segment); this._length=0; }
        length() { return this._length || (this._length = this.segments.reduce((a,c) => a+c.length(), 0)); }
        p(t) {
            t = Math.max(Math.min(t,1),0)*this.length();
            for (let l=0,i=0,sl=0; i<this.segments.length; i++,l+=sl) {
                sl=this.segments[i].length();
                if (t>=l&&t<=l+sl) return this.segments[i].p(sl===0?0:(t-l)/sl, sl/this.length());
            }
            return this.segments[this.segments.length-1].p(1);
        }
    }
    return new Path(svg);
}

function Polygons() {
    const t=[], s=25;
    const e=Array.from({length:s**2}, ()=>[]);
    const n=class {
        constructor() { this.cp=[]; this.dp=[]; this.aabb=[]; }
        addPoints(...t) {
            let s=1e5,e=-1e5,n=1e5,h=-1e5;
            (this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]);e=Math.max(e,t[0]);n=Math.min(n,t[1]);h=Math.max(h,t[1]);});
            this.aabb=[s,n,e,h];
        }
        addSegments(...t) { t.forEach(t=>this.dp.push(t)); }
        draw(t) { for (let s=0,e=this.dp.length;s<e;s+=2) t.jump(this.dp[s]),t.goto(this.dp[s+1]); }
        inside(t) {
            let s=0;
            for (let e=0,n=this.cp.length;e<n;e++) this.segment_intersect(t,[.1,-1e3],this.cp[e],this.cp[(e+1)%n])&&s++;
            return 1&s;
        }
        boolean(t,s=true) {
            const e=[];
            for (let n=0,h=this.dp.length;n<h;n+=2) {
                const h=this.dp[n],o=this.dp[n+1],a=[];
                for (let s=0,e=t.cp.length;s<e;s++) { const n=this.segment_intersect(h,o,t.cp[s],t.cp[(s+1)%e]); if(n!==false)a.push(n); }
                if(a.length===0) { s===!t.inside(h)&&e.push(h,o); }
                else {
                    a.push(h,o);
                    const n=o[0]-h[0],i=o[1]-h[1];
                    a.sort((t,s)=>(t[0]-h[0])*n+(t[1]-h[1])*i-(s[0]-h[0])*n-(s[1]-h[1])*i);
                    for (let n=0;n<a.length-1;n++) (a[n][0]-a[n+1][0])**2+(a[n][1]-a[n+1][1])**2>=.001&&s===!t.inside([(a[n][0]+a[n+1][0])/2,(a[n][1]+a[n+1][1])/2])&&e.push(a[n],a[n+1]);
                }
            }
            return (this.dp=e).length>0;
        }
        segment_intersect(t,s,e,n) {
            const h=(n[1]-e[1])*(s[0]-t[0])-(n[0]-e[0])*(s[1]-t[1]);
            if(h===0)return false;
            const o=((n[0]-e[0])*(t[1]-e[1])-(n[1]-e[1])*(t[0]-e[0]))/h;
            const a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/h;
            return o>=0&&o<=1&&a>=0&&a<=1&&[t[0]+o*(s[0]-t[0]),t[1]+o*(s[1]-t[1])];
        }
    };
    return {
        create: ()=>new n,
        draw: (n,h,o=true)=>{
            let r=(function(n){const h={},o=200/s;for(var a=0;a<s;a++){const c=a*o-100,r=[0,c,200,c+o];if(n[3]<r[1]||n[1]>r[3])continue;for(var i=0;i<s;i++){const c=i*o-100;r[0]=c;r[2]=c+o;if(n[0]>r[2]||n[2]<r[0])continue;e[i+a*s].forEach(s=>{const e=t[s];if(n[3]<e.aabb[1]||n[1]>e.aabb[3]||n[0]>e.aabb[2]||n[2]<e.aabb[0])return;h[s]=1;});}}return Array.from(Object.keys(h),s=>t[s]);})(h.aabb);
            for(let t=0;t<r.length&&h.boolean(r[t]);t++);
            h.draw(n);
            if(o){t.push(h);const a=t.length-1,c=200/s;e.forEach((t,e)=>{const n=e%s*c-100,i=(e/s|0)*c-100,r=[n,i,n+c,i+c];if(r[3]<h.aabb[1]||r[1]>h.aabb[3]||r[0]>h.aabb[2]||r[2]<h.aabb[0])return;t.push(a);});}
        }
    };
}