(function (window, document) {

var slice = [].slice, dToR = Math.PI / 180, define;

function makeFn(args, fn) {
    return Function(
        args.concat('__canvas,__canvasElement,__turtle,__args,__extras').join(','),
        'return (' + fn + ').apply(this, __extras.concat(__args))'
    );
}

function turtleGraphics(canvas, draw) {
    if (typeof canvas == 'string')
        canvas = document.querySelector(canvas);
    var turtle = {
        x: 0,
        y: 0,
        heading: 0,
        drawing: true
    },  context = canvas.getContext('2d'),
        keys = Object.keys(turtleGraphics.functions),
        values = keys.map(function (key) { return turtleGraphics.functions[key]; }).map(function (fn) {
            var fun = makeFn(keys, fn.fn);
            return function () {
                return fun.apply(this, args(arguments, fn.args));
            };
        }), returnVal;
    function args(args, extras) {
        return values.concat(context, canvas, turtle, [slice.call(args)], [extras]);
    }
    context.beginPath();
    context.moveTo(turtle.x, turtle.y);
    returnVal = makeFn(keys, draw).apply(this, args(arguments, []));
    context.closePath();
    return returnVal;
}

turtleGraphics.functions = {};
define = turtleGraphics.define = function (name, fn, args) {
    turtleGraphics.functions[name] = {fn: fn, args: (args || []).concat(fn.args || [])};
};

define('penup', function () {
    __turtle.drawing = false;
});

define('pendown', function () {
    __turtle.drawing = true;
});

define('color', function () {
    return __canvas.strokeStyle;
});

define('setcolor', function (color) {
    __canvas.strokeStyle = color;
});

define('width', function () {
    return __canvas.lineWidth;
});

define('setwidth', function (width) {
    __canvas.lineWidth = width;
});

define('pos', function () {
    return {x: __turtle.x, y: __turtle.y};
});

define('setpos', function (x, y) {
    __turtle.x = x;
    __turtle.y = y;
});

define('heading', function () {
    return __turtle.heading;
});

define('setheading', function (val) {
    __turtle.heading = val;
});

define('clearscreen', function () {
    __turtle.x = 0;
    __turtle.y = 0;
    __turtle.heading = 0;
    __turtle.drawing = true;
    __canvas.save();
    __canvas.lineWidth = 1;
    __canvas.strokeStyle = '#000000';
    __canvas.setTransform(1, 0, 0, 1, 0, 0);
    __canvas.clearRect(0, 0, __canvasElement.width, __canvasElement.height);
    __canvas.restore();
});

function newCoord(old, dist, heading, add, trig) {
    return Math.round(add(old, dist * trig(heading * dToR)));
}

function mover(add) {
    function fn(add, newCoord, dist) {
        var newX = newCoord(__turtle.x, dist, __turtle.heading, add, Math.sin),
            newY = newCoord(__turtle.y, dist, __turtle.heading, add, Math.cos);
        __turtle.x = newX;
        __turtle.y = newY;
        if (__turtle.drawing)
            __canvas.lineTo(newX, newY);
        else
            __canvas.moveTo(newX, newY);
        __canvas.stroke();
    }
    fn.args = [add, newCoord];
    return fn;
}

function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }

define('forward', mover(add));

define('back', mover(subtract));

function rotator(add) {
    function fn(add, deg) {
        return __turtle.heading = add(__turtle.heading, deg) % 360;
    }
    fn.args = [add];
    return fn;
}

define('left', rotator(subtract));

define('right', rotator(add));

window.turtleGraphics = turtleGraphics;

})(this, document);
