function drawCircle(context, x, y, r, color) {
    context.strokeStyle = color;
    context.setLineDash([]);
    context.beginPath();
    context.arc(x, y, r, 0, 2 * Math.PI, false);
    context.closePath();
    context.stroke();
}

function fillCircle(context, x, y, r, color, contourColor) {
    context.fillStyle = color;
    context.setLineDash([]);
    context.beginPath();
    context.arc(x, y, r, 0, 2 * Math.PI, false);
    context.closePath();
    context.fill();
    if (contourColor != null) {
        context.strokeStyle = contourColor;
        context.stroke();
    }
}

function fillEllipse(context, x, y, rX, rY, rotation, color, contourColor) {
    if (rX == rY) {
        fillCircle(context, x, y, rX, color, contourColor);
        // for better compatibility
        return;
    }
    if (context.ellipse == null) {
        // for old browsers (MSIE)
        fillCircle(context, x, y, Math.min(rX, rY), color, contourColor);
        return;
    }
    context.fillStyle = color;
    context.setLineDash([]);
    context.beginPath();
    context.ellipse(x, y, rX, rY, rotation, 0, 2 * Math.PI, false);
    context.closePath();
    context.fill();
    if (contourColor != null) {
        context.strokeStyle = contourColor;
        context.stroke();
    }
}

function drawArrow(context, x1, y1, x2, y2, arrowLength, arrowWidth, color) {
    var dx = x2 - x1, dy = y2 - y1;
    var length = Math.sqrt(dx * dx + dy * dy);
    var xm = length - arrowLength, xn = xm, ym = arrowWidth, yn = -arrowWidth;
    var sin = dy / length, cos = dx / length;

    var x = xm * cos - ym * sin + x1;
    ym = xm * sin + ym * cos + y1;
    xm = x;

    x = xn * cos - yn * sin + x1;
    yn = xn * sin + yn * cos + y1;
    xn = x;

    context.strokeStyle = color;
    context.beginPath();
    context.moveTo(x1, y1);
    context.lineTo((xm + xn) / 2, (ym + yn) / 2);
    context.stroke();

    context.fillStyle = color;
    context.beginPath();
    context.moveTo(x2, y2);
    context.lineTo(xm, ym);
    context.lineTo(xn, yn);
    context.fill();
}

function rotateAroundX(points3D, angle) {
    return angle == 0.0 ? points3D : rotateAroundXCosSin(points3D, Math.cos(angle), Math.sin(angle));
}

function rotateAroundXCosSin(points3D, cos, sin) {
    var result = [];
    for (var k = 0; k < points3D.length; k++) {
        var p = points3D[k];
        var x = p.x;
        var y = p.y * cos - p.z * sin;
        var z = p.y * sin + p.z * cos;
        result.push({x: x, y: y, z: z});
    }
    return result;
}

function rotateAroundY(points3D, angle) {
    return angle == 0.0 ? points3D : rotateAroundYCosSin(points3D, Math.cos(angle), Math.sin(angle));
}

function rotateAroundYCosSin(points3D, cos, sin) {
    var result = [];
    for (var k = 0; k < points3D.length; k++) {
        var p = points3D[k];
        var z = p.z * cos - p.x * sin;
        var x = p.z * sin + p.x * cos;
        var y = p.y;
        result.push({x: x, y: y, z: z});
    }
    return result;
}

function rotateAroundZ(points3D, angle) {
    return angle == 0.0 ? points3D : rotateAroundZCosSin(points3D, Math.cos(angle), Math.sin(angle));
}

function rotateAroundZCosSin(points3D, cos, sin) {
    var result = [];
    for (var k = 0; k < points3D.length; k++) {
        var p = points3D[k];
        var x = p.x * cos - p.y * sin;
        var y = p.x * sin + p.y * cos;
        var z = p.z;
        result.push({x: x, y: y, z: z});
    }
    return result;
}

function between(firstPoint3D, secondPoint3D, part) {
    return {
        x: firstPoint3D.x * part + secondPoint3D.x * (1.0 - part),
        y: firstPoint3D.y * part + secondPoint3D.y * (1.0 - part),
        z: firstPoint3D.z * part + secondPoint3D.z * (1.0 - part)
    };
}

var ___figuresAndDrawingFunctions = {};
var ___figuresAndDrawingTimers = {};
var ___figuresAndDrawingObjects = {};
var ___figuresAndDrawingFunctionsCalled = false;

function addCanvasFigure(id, fn, delayForTimer) {
    ___figuresAndDrawingFunctions[id] = fn;
    if (delayForTimer != null) {
        setCanvasTimer(id, fn, delayForTimer);
    }
}

function setCanvasTimer(id, fn, delay) {
    if (fn == null) {
        fn = ___figuresAndDrawingFunctions[id];
        if (fn == null) {
            alert("No function for " + id);
            return;
        }
    }
    clearCanvasTimer(id);
    ___figuresAndDrawingObjects[id] = {};
    var caller = function() {
         var canvas = document.getElementById(id);
         if (canvas && canvas.getContext) {
             var context = canvas.getContext("2d");
             var object = ___figuresAndDrawingObjects[id];
             context.clearRect(0, 0, canvas.width, canvas.height);
             fn(context, canvas, object);
         }
         ___figuresAndDrawingTimers[id] = setTimeout(caller, delay);
    };
    ___figuresAndDrawingTimers[id] = setTimeout(caller, delay);

}

function clearCanvasTimer(id) {
    var timer = ___figuresAndDrawingTimers[id];
    if (timer != null) {
        clearTimeout(timer);
    }
}


function ___drawAllCanvasFigures() {
    if (___figuresAndDrawingFunctionsCalled) {
        return;
    }
    for (id in ___figuresAndDrawingFunctions) {
        var canvas = document.getElementById(id);
        if (canvas && canvas.getContext) {
            ___figuresAndDrawingFunctions[id](canvas.getContext("2d"), canvas, {});
            // - last argument [] may be useful if the function is called also from timer
        }
    }
    ___figuresAndDrawingFunctionsCalled = true;
    // - necessary in some browsers
}

window.addEventListener('DOMContentLoaded', ___drawAllCanvasFigures);
// No sense to support IE8- window.attachEvent, because canvas appeared since IE9.
