VCanvas_canvas = createClass(VCanvas_base, {

init: function (width, height, target, interact) {
    VCanvas_canvas._super.init.call(this, width, height, target);
    this.canvas = document.createElement('canvas');
    if (target[0]) {
        target = target[0];
    }
    $.data(target, '_jqs_vcanvas', this);
    $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' });
    this._insert(this.canvas, target);
    this._calculatePixelDims(width, height, this.canvas);
    this.canvas.width = this.pixelWidth;
    this.canvas.height = this.pixelHeight;
    this.interact = interact;
    this.shapes = {};
    this.shapeseq = [];
    this.currentTargetShapeId = undefined;
    $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight});
},

_getContext: function (lineColor, fillColor, lineWidth) {
    var context = this.canvas.getContext('2d');
    if (lineColor !== undefined) {
        context.strokeStyle = lineColor;
    }
    context.lineWidth = lineWidth === undefined ? 1 : lineWidth;
    if (fillColor !== undefined) {
        context.fillStyle = fillColor;
    }
    return context;
},

reset: function () {
    var context = this._getContext();
    context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
    this.shapes = {};
    this.shapeseq = [];
    this.currentTargetShapeId = undefined;
},

_drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
    var context = this._getContext(lineColor, fillColor, lineWidth),
        i, plen;
    context.beginPath();
    context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5);
    for (i = 1, plen = path.length; i < plen; i++) {
        context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines
    }
    if (lineColor !== undefined) {
        context.stroke();
    }
    if (fillColor !== undefined) {
        context.fill();
    }
    if (this.targetX !== undefined && this.targetY !== undefined &&
        context.isPointInPath(this.targetX, this.targetY)) {
        this.currentTargetShapeId = shapeid;
    }
},

_drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
    var context = this._getContext(lineColor, fillColor, lineWidth);
    context.beginPath();
    context.arc(x, y, radius, 0, 2 * Math.PI, false);
    if (this.targetX !== undefined && this.targetY !== undefined &&
        context.isPointInPath(this.targetX, this.targetY)) {
        this.currentTargetShapeId = shapeid;
    }
    if (lineColor !== undefined) {
        context.stroke();
    }
    if (fillColor !== undefined) {
        context.fill();
    }
},

_drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
    var context = this._getContext(lineColor, fillColor);
    context.beginPath();
    context.moveTo(x, y);
    context.arc(x, y, radius, startAngle, endAngle, false);
    context.lineTo(x, y);
    context.closePath();
    if (lineColor !== undefined) {
        context.stroke();
    }
    if (fillColor) {
        context.fill();
    }
    if (this.targetX !== undefined && this.targetY !== undefined &&
        context.isPointInPath(this.targetX, this.targetY)) {
        this.currentTargetShapeId = shapeid;
    }
},

_drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
    return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor);
},

appendShape: function (shape) {
    this.shapes[shape.id] = shape;
    this.shapeseq.push(shape.id);
    this.lastShapeId = shape.id;
    return shape.id;
},

replaceWithShape: function (shapeid, shape) {
    var shapeseq = this.shapeseq,
        i;
    this.shapes[shape.id] = shape;
    for (i = shapeseq.length; i--;) {
        if (shapeseq[i] == shapeid) {
            shapeseq[i] = shape.id;
        }
    }
    delete this.shapes[shapeid];
},

replaceWithShapes: function (shapeids, shapes) {
    var shapeseq = this.shapeseq,
        shapemap = {},
        sid, i, first;

    for (i = shapeids.length; i--;) {
        shapemap[shapeids[i]] = true;
    }
    for (i = shapeseq.length; i--;) {
        sid = shapeseq[i];
        if (shapemap[sid]) {
            shapeseq.splice(i, 1);
            delete this.shapes[sid];
            first = i;
        }
    }
    for (i = shapes.length; i--;) {
        shapeseq.splice(first, 0, shapes[i].id);
        this.shapes[shapes[i].id] = shapes[i];
    }

},

insertAfterShape: function (shapeid, shape) {
    var shapeseq = this.shapeseq,
        i;
    for (i = shapeseq.length; i--;) {
        if (shapeseq[i] === shapeid) {
            shapeseq.splice(i + 1, 0, shape.id);
            this.shapes[shape.id] = shape;
            return;
        }
    }
},

removeShapeId: function (shapeid) {
    var shapeseq = this.shapeseq,
        i;
    for (i = shapeseq.length; i--;) {
        if (shapeseq[i] === shapeid) {
            shapeseq.splice(i, 1);
            break;
        }
    }
    delete this.shapes[shapeid];
},

getShapeAt: function (el, x, y) {
    this.targetX = x;
    this.targetY = y;
    this.render();
    return this.currentTargetShapeId;
},

render: function () {
    var shapeseq = this.shapeseq,
        shapes = this.shapes,
        shapeCount = shapeseq.length,
        context = this._getContext(),
        shapeid, shape, i;
    context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
    for (i = 0; i < shapeCount; i++) {
        shapeid = shapeseq[i];
        shape = shapes[shapeid];
        this['_draw' + shape.type].apply(this, shape.args);
    }
    if (!this.interact) {
        // not interactive so no need to keep the shapes array
        this.shapes = {};
        this.shapeseq = [];
    }
}

});