OFFSET = 0;

function lawn_qsort(a, k, l, r) {
    // i: stack index, s: stack,
    // p: pivot index, v: pivot value,
    // t: temporary array item,
    // x, y: partion low/high

    var i, s, p, v, t, x, y;

    l = l || 0;
    r = r || a.length - 1;

    i = 2;
    s = [l, r];

    while (i > 0) {
        r = s[--i];
        l = s[--i];

        if (l < r) {
            // partition

            x = l;
            y = r - 1;

            p = l;
            v = a[p];
            a[p] = a[r];

            while (true) {
                while (
                    x <= y &&
                    a[x] != undefined &&
                    a[x][k] < v[k]) {
                    x++;
                }
                while (
                    x <= y &&
                    a[y] != undefined &&
                    a[y][k] >= v[k]) {
                    y--;
                }
                if (x > y) {
                    break;
                }
                t = a[x];
                a[x] = a[y];
                a[y] = t;
            }

            a[r] = a[x];
            a[x] = v;

            // end

            s[i++] = l;
            s[i++] = x - 1;
            s[i++] = x + 1;
            s[i++] = r;
        }
    }
}


function lawn_canvas(width, height) {
    var cnv;

    cnv = document.createElement('canvas');
    cnv.setAttribute('width', width || '512');
    cnv.setAttribute('height', height || '512');
    cnv.setAttribute('style', 'background:transparent');
    document.body.appendChild(cnv);

    return cnv;
}


function lawn_rotate(point, theta) {
    var m, sin_theta, cos_theta;

    sin_theta = Math.sin(theta);
    cos_theta = Math.cos(theta);

    m = [
        cos_theta, -sin_theta,
        sin_theta, cos_theta
    ];

    return [
        m[0] * point[0] + m[1] * point[1],
        m[2] * point[0] + m[3] * point[1]
    ];
}


function lawn_gradient(resolution, stops) {
    var colors = [];
    for (var i = 0; i < stops.length - 1; i++) {
        var r = resolution * stops[i + 1][0] - resolution * stops[i][0];
        for (var n = 0; n < r + 1; n++) {
            var c = [];
            for (var m = 1; m < 4; m++) {
                c.push(parseInt((stops[i + 1][m] - stops[i][m]) * (n / r) + stops[i][m]));
            }
            colors.push('rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')');
        }
    }
    return colors;
}


function lawn_draw(ctx, azimuth, theta, zspace, shapes) {
    /*

      {
          x: <num>, y: <num>, z: <num>,
          slices: [x1, y1, x2, y2, ...]
      }

    */
    
    var atoms, shape, path, n, cx, cy;

    atoms = [];
    for (var i = 0, l = shapes.length; i < l; i++) {
        shape = shapes[i];
        for (var j = 0, m = shape.slices.length; j < m; j++) {
            path = shape.slices[j];

            n = cx = cy = 0;
            while (n < path.length) {
                cx += path[n++]; cy += path[n++];
            }
            cx /= path.length / 2; cy /= path.length / 2;

            atoms.push({
                x: shape.x, y: shape.y, z: shape.z + j, path: path,
                sort: (shape.z + j) + lawn_rotate([cx, cy], theta)[1] * .00001,
                color: shape.palette[(j + OFFSET) % shape.palette.length]
            });
        }
    }

    lawn_qsort(atoms, 'sort');

    ctx.save();

    if ($.client.os == 'Mac') {
        ctx.shadowBlur = 16;
    } else {
        ctx.shadowBlur = 32;
    }

    for (var n, i = 0, l = atoms.length; i < l; i++) {
        ctx.save();
        ctx.translate(0, -atoms[i].z * zspace);
        ctx.scale(1, azimuth);
        ctx.rotate(theta);

        n = 0;
        path = atoms[i].path;

        ctx.translate(atoms[i].x, atoms[i].y);
        ctx.beginPath();
        ctx.moveTo(path[n++], path[n++]);
        while (n < path.length) {
            ctx.lineTo(path[n++], path[n++]);
        }
        ctx.fillStyle = atoms[i].color;
        ctx.shadowColor = atoms[i].color;
        ctx.fill();
        ctx.restore();
    }

    ctx.restore();

    OFFSET++;
}

