function NPlayer(targetContainer, width, height, data, buttonsCode, displayTextArea, speed) {
    this.targetContainer = document.getElementById(targetContainer);
    this.width = width;
    this.height = height;
    this.data = data;
    this.buttonsCode = buttonsCode;
    this.displayTextArea = displayTextArea;
    this.pointSize = 3;
    this.textSpace = this.pointSize + 4;
    this.textSize = 20;
    this.textIndexCoeficient = 0.7;
    this.textYposCoeficient = 0.2;
    
    this.clineWidth = 1;
    this.clineColor = "#3fd866";
    this.clineLabel = "l";
    
    this.direction = 0; // 1 = dopredu, 0 = zastaveno, -1 = dozadu
    
    this.speed = speed; // rychlost animace px/s
    this.position = data.limits.start; // pozice v animaci == y zametaci primky
    
    this.nextBreak = 1; // indexy v data.breakPoints
    this.prevBreak = 0;
    
    this.oldTime = 0;
    this.delta = 0;
    
    this.init();
    
    this.actText();
}

NPlayer.prototype.init = function() {
    var canvas = document.createElement("canvas");
    canvas.setAttribute("width", this.width);
    canvas.setAttribute("height", this.height);
    this.ctx = canvas.getContext("2d");
    this.ctx.textBaseline = 'bottom'; // set text draw to bottom
    
    this.targetContainer.setAttribute("style", "width: " + (this.width + 2) + "px;");
    this.targetContainer.appendChild(canvas);
    
    canvas.addEventListener("click", this.setLine.bind(this));
    
    this.showControls(this.buttonsCode);
    this.initTextArea();
    
    this.redrawScene(); // replaced by animationLoop
    requestAnimationFrame(this.animationLoop.bind(this));
}

NPlayer.prototype.setLine = function(evt) {
    this.pause(this)();
//    return function() {
//        alert(e.pageY - e.offsetTop);
//        lineYPos = evt.clientY; - canvas.getBoundingClientRect().top;
        lineYPos = evt.offsetY == undefined ? evt.layerY : evt.offsetY;
//        console.log(lineYPos);
//        console.log(Obj);
//    }
    if (lineYPos < this.data.limits.start) {
        this.position = this.data.limits.start;
    } else if (lineYPos > this.data.limits.end) {
        this.position = this.data.limits.end;
    } else {
        this.position = lineYPos;  
    }
    this.set_break_points_from_position.call(this);
//    this.checkState();
//    this.lineY = lineYPos;
    this.redrawScene();
}

// animation loop
NPlayer.prototype.animationLoop = function(currentTime) {
//    console.log(currentTime);
//    currentTime = performance.now();
    this.delta = currentTime - this.oldTime;
    
    if (this.direction != 0 && this.checkState()) { // if moving
        this.incY = calcDistanceToMove(this.delta, this.speed);
        this.position += this.direction * this.incY;
    
        this.redrawScene();
    }
    
    this.oldTime = currentTime;
    requestAnimationFrame(this.animationLoop.bind(this));
}

NPlayer.prototype.checkState = function() {
    var breakPoints = this.data.breakPoints;
    if (this.direction == 1) {
        if (this.position >= breakPoints[this.nextBreak] - this.incY) {
            this.pause(this)();
            this.position = breakPoints[this.nextBreak];
            this.redrawScene();
            
            this.prevBreak = this.nextBreak - 1;
            if (breakPoints.length > this.nextBreak + 1) {
                this.nextBreak++;
            }
//            console.log("FW",this.prevBreak, this.nextBreak);
            return false;
        }
    } else {
        if (this.position <= breakPoints[this.prevBreak] + this.incY) {
            this.pause(this)();
            this.position = breakPoints[this.prevBreak];
            this.redrawScene();
            
            this.nextBreak = this.prevBreak + 1;
            if (0 < this.prevBreak) {
                this.prevBreak--;
            }
//            console.log("BW",this.prevBreak, this.nextBreak);
            return false;
        }
    }
//    console.log("check return true");
    return true;
}

// prekresli celou scenu
NPlayer.prototype.redrawScene = function() {
    console.log(this.position);
    this.clearDrawing();
//    console.log(this.position, this.nextBreak, this.position >= this.data.breakPoints[this.nextBreak]);
//    this.ctx.beginPath();
//    this.ctx.arc(100*Math.random(), 100*Math.random(), 20, 0, 2 * Math.PI);
//    this.ctx.fillStyle = "#159";
//    this.ctx.fill();
    
    this.drawControlLine();
    
//    this.drawArrays();
    this.drawLines();
    this.drawPoints();
    this.drawLabels();
//    
    this.actText();
}

// shows control buttons of animation 0 == no, 1 == jump keyframe FW,BW, 2 == play FW,BW, 3 == all
NPlayer.prototype.showControls = function(controlsID) {
//    alert(controlsID);
    if (controlsID > 0 && controlsID < 4) {
        // init control frame
        var ctrlFrame = document.createElement("div");
        ctrlFrame.setAttribute("class", "controlFrame");
        width = (controlsID < 3) ? 80 : 160;
        
        ctrlFrame.setAttribute("style", "width: "+ width +"px;")
        // init buttons
        // jump BW
        if (controlsID == 1 || controlsID == 3) {
            this.jumpBWB = document.createElement("input");
            this.jumpBWB.setAttribute("type", "button");
//            this.jumpBWB.setAttribute("value", "JBW");
            this.jumpBWB.setAttribute("class", "jumpBW");
    
            this.jumpBWB.addEventListener("click", this.jump(this, -1));
            ctrlFrame.appendChild(this.jumpBWB);
        }
//        // play pause
        if (controlsID == 2 || controlsID == 3) {
            this.playBWB = document.createElement("input");
            this.playBWB.setAttribute("type", "button");
//            this.playBWB.setAttribute("value", "Play<");
            this.playBWB.setAttribute("class", "playBW");
    
            this.playBWB.addEventListener("click", this.playBW(this));
            ctrlFrame.appendChild(this.playBWB);
            
            this.playFWB = document.createElement("input");
            this.playFWB.setAttribute("type", "button");
//            this.playFWB.setAttribute("value", "Play>");
            this.playFWB.setAttribute("class", "playFW");
    
            this.playFWB.addEventListener("click", this.playFW(this));
            ctrlFrame.appendChild(this.playFWB);
            
            this.pauseB = document.createElement("input");
            this.pauseB.setAttribute("type", "button");
//            this.pauseB.setAttribute("value", "Pause");
            this.pauseB.setAttribute("class", "pause");
            this.pauseB.setAttribute("style", "display: none;");
    
            this.pauseB.addEventListener("click", this.pause(this));
            ctrlFrame.appendChild(this.pauseB);
        }
        //jump FW
        if (controlsID == 1 || controlsID == 3) {
            this.jumpFWB = document.createElement("input");
            this.jumpFWB.setAttribute("type", "button");
//            this.jumpFWB.setAttribute("value", "JFW");
            this.jumpFWB.setAttribute("class", "jumpFW");
    
            this.jumpFWB.addEventListener("click", this.jump(this, 1));
            ctrlFrame.appendChild(this.jumpFWB);
        }
        this.targetContainer.appendChild(ctrlFrame);
    }
}

NPlayer.prototype.jump = function(Obj, direction) {
    return function() {
        //Obj.pause(Obj)();
        //Obj.lineY = Obj.getNextPosition(Obj.linePoints, Obj.lineY, direction);
//        console.log(Obj.lineY);
//        console.log("pred", Obj.position, Obj.prevBreak, Obj.nextBreak);
        Obj.pause(Obj)();
        var breakPoints = Obj.data.breakPoints;
        if (direction == -1) {
            if (Obj.position <= breakPoints[0]) {
//                Obj.position = 0;
                return;
            } else { // TODO if position == prevBreak -> prevBreak-- (i dale v else vetvi)
//                if (Obj.position == breakPoints[Obj.prevBreak]) {
//                    Obj.prevBreak--;
//                }
                Obj.position = breakPoints[Obj.prevBreak];
                Obj.nextBreak = Obj.prevBreak + 1;
                if (0 < Obj.prevBreak) {
                    Obj.prevBreak--;
                }
            }
        } else {
            if (Obj.position >= breakPoints[breakPoints.length - 1]) {
//                Obj.position = 0;
                return;
            } else {
                Obj.position = breakPoints[Obj.nextBreak];
                Obj.prevBreak = Obj.nextBreak - 1;
                if (breakPoints.length > Obj.nextBreak + 1) {
                    Obj.nextBreak++;
                }
            }
        }
//        console.log("post", Obj.position, Obj.prevBreak, Obj.nextBreak);
        Obj.redrawScene();
    }
}

NPlayer.prototype.set_break_points_from_position = function() {
    for (var i = 0; i < this.data.breakPoints.length; i++) {
//        console.log(this.position, i, this.data.breakPoints[i]);
        if (this.position <= this.data.breakPoints[i]) {
            this.prevBreak = (this.position == this.data.breakPoints[i] || i > 0) ? i-1 : i;
            this.nextBreak = (this.position == this.data.breakPoints[i] && i < (this.data.breakPoints.length - 1)) ? i+1 : i;
            break;
        }
    }
//    console.log(this.position, this.prevBreak,this.nextBreak,this.data.breakPoints);
}

// vrati pristi pozici zametaci primky z pole podle smeru (1 == vpred 0 == vzad)
//NPlayer.prototype.getNextPosition = function(array, actualPos, direction) {
//    if (direction == 1) {
//        if (actualPos >= array[array.length - 1]) {
//            return array[array.length - 1];
//        }
//        for (var i = 0; i < array.length; i++) {
//            if (array[i] > actualPos) {
//                return array[i];
//            }
//        }
//    } else {
//        if (actualPos <= array[0]) {
//            return array[0];
//        }
//        for (var i = array.length - 1; i >= 0 ; i--) {
////            console.log(array[i]);
//            if (array[i] < actualPos) {
//                return array[i];
//            }
//        }
//    }
//}

NPlayer.prototype.pause = function(Obj) {
    return function() {
//        clearInterval(Obj.intervalID);
        Obj.direction = 0;
        Obj.playFWB.setAttribute("style", "display: block;");
        Obj.playBWB.setAttribute("style", "display: block;");
        Obj.pauseB.setAttribute("style", "display: none;");
//        Obj.direction = 1;
        // pokud mimo animaci - restartuj ji do vychozi polohy jinak prehravej z akt. polohy
//        if (Obj.lineY <= Obj.lineAnim[0] || Obj.lineY >= Obj.lineAnim[1]) {
//            Obj.lineY = Obj.lineAnim[0];
//        }
//        Obj.animator(Obj);
    }
}

NPlayer.prototype.playFW = function(Obj) {
    return function() {
        Obj.playFWB.setAttribute("style", "display: none;");
        Obj.playBWB.setAttribute("style", "display: none;");
        Obj.pauseB.setAttribute("style", "display: block;");
        Obj.pauseB.focus();
//        Obj.direction = 1;
        // pokud mimo animaci - restartuj ji do vychozi polohy jinak prehravej z akt. polohy
        if (Obj.position <= Obj.data.limits.start || Obj.position >= Obj.data.limits.end) {
            Obj.position = Obj.data.limits.start;
            Obj.prevBreak = 0;
            Obj.nextBreak = 1;
        }
        
        // fix jumps breakPoints
        if (Obj.nextBreak - Obj.prevBreak > 1) {
            Obj.prevBreak++;
        }
        
//        Obj.animator(Obj);
        Obj.direction = 1;
    }
}

NPlayer.prototype.playBW = function(Obj) {
    return function() {
        Obj.playFWB.setAttribute("style", "display: none;");
        Obj.playBWB.setAttribute("style", "display: none;");
        Obj.pauseB.setAttribute("style", "display: block;");
        Obj.pauseB.focus();
//        Obj.direction = 0;
        // pokud mimo animaci - restartuj ji do vychozi polohy jinak prehravej z akt. polohy
        if (Obj.position <= Obj.data.limits.start || Obj.position >= Obj.data.limits.end) {
            Obj.position = Obj.data.limits.end;
            // TODO zmenit Obj.nextBreak prevBreak?
            Obj.prevBreak = Obj.data.breakPoints.length - 2;
            Obj.nextBreak = Obj.data.breakPoints.length - 1;
        }
        
        // fix jumps breakPoints
        if (Obj.nextBreak - Obj.prevBreak > 1) {
            Obj.nextBreak--;
        }
        
//        Obj.animator(Obj);
        Obj.direction = -1;
    }
}

//NPlayer.prototype.animator = function(Obj) {
//    Obj.intervalID = setInterval(Obj.play(Obj), 1000/Obj.framerate);
//}

//NPlayer.prototype.play = function(Obj) {
//    return function() {
////        console.log(Obj.lineY);
//        if (Obj.direction == 1 && Obj.lineY < Obj.lineAnim[1]) {
//            Obj.lineY++;
//        } else if (Obj.direction == 0 && Obj.lineY > Obj.lineAnim[0]) {
//    //        Obj.redrawScene();
//            Obj.lineY--;
//        } else {
////            console.log("Clearing Interval");
//            clearInterval(Obj.intervalID);
//            Obj.playFWB.setAttribute("style", "display: block;");
//            Obj.playBWB.setAttribute("style", "display: block;");
//            Obj.pauseB.setAttribute("style", "display: none;");
//        }
//        Obj.redrawScene();
//    }
//}

// vymaze canvas
NPlayer.prototype.clearDrawing = function() {
    this.ctx.clearRect(0, 0, this.width, this.height);
}

// vykresli zametaci primku
NPlayer.prototype.drawControlLine = function() {
    this.drawLine(20, this.position, this.width - 30, this.position, this.clineColor, this.clineWidth, false); // zametaci primka
    this.drawLabelDesc([this.width - 30, this.position, this.clineLabel, this.clineColor, 20, 2]); // popisek zametaci primky
}

// vykresli popisků
NPlayer.prototype.drawLabels = function() {
//    var labelData;
    for (var i = 0; i < this.data.objects.length; i++) {
        var currentObject = this.data.objects[i];
        if (currentObject.f <= this.position && currentObject.t >= this.position) {
            var labels = currentObject.cont.popisky;
            for (var j = 0; j < labels.length; j++) {
                this.drawLabelDesc(labels[j]);
            }
        }
    }
//    for (var i = 0; i < this.dataArray[this.position][1].length; i++) {
//        data = this.dataArray[this.position][1][i];
//        this.drawLabelDesc(data);
//    }
}

//vykresleni popisku
NPlayer.prototype.drawLabelDesc = function(point) {
    var x1 = point[0], y1 = point[1];
    
    var color = point[3] == "" ? "#000" : point[3];
    if (point[2] != "") {
        this.drawText3(x1, y1, point[2], color, point[4], point[5]);
    }
}

// vykresli cary
NPlayer.prototype.drawLines = function() {
    var lineData;
    for (var i = 0; i < this.data.objects.length; i++) {
//        var pos = this.dataArray[this.position][1][i][0];
//        var x1 = this.dataArray[this.position][0][pos][0];
//        var y1 = this.dataArray[this.position][0][pos][1];
//        
//        var pos = this.dataArray[this.position][1][i][1];
//        var x2 = this.dataArray[this.position][0][pos][0];
//        var y2 = this.dataArray[this.position][0][pos][1];
//        
//        var color = this.dataArray[this.position][1][i][2] == "" ? undefined : this.dataArray[this.position][1][i][2];
        var currentObject = this.data.objects[i];
        if (currentObject.f <= this.position && currentObject.t >= this.position) {
            var lines = currentObject.cont.spoje;
            for (var j = 0; j < lines.length; j++) {
                lineData = lines[j];
        
                var color = lineData[4] == "" ? undefined : lineData[4];
        
                this.drawLine(lineData[0], lineData[1], lineData[2], lineData[3], color, lineData[5], false);
            }
        }
    }
}

// vykresli body s popisky
NPlayer.prototype.drawPoints = function() {
    for (var i = 0; i < this.data.objects.length; i++) {
        var currentObject = this.data.objects[i];
        if (currentObject.f <= this.position && currentObject.t >= this.position) {
            var points = currentObject.cont.body;
            for (var j = 0; j < points.length; j++) {
                this.drawPoint(points[j]);
            }
        }
    }
    
//    for (var i = 0; i < this.dataArray[this.position][0].length; i++) {
//        this.drawPoint(this.dataArray[this.position][0][i]);
//    }
}

//vykresleni popisku bodu
NPlayer.prototype.drawPoint = function(point) {
    var x1 = point[0], y1 = point[1];
    var textColor = point[3] == "" ? "#000" : point[3];
    var fillColor = point[5] == "" ? "#000" : point[5];
    var outerColor = point[6] == "" ? "#000" : point[6];
//    drawLine(x1-3, y1-3, x1+3, y1+3, "#000", 1, ctx);
//    drawLine(x1-3, y1+3, x1+3, y1-3, "#000", 1, ctx);
    if (fillColor != null) {
        this.ctx.beginPath();
        this.ctx.arc(x1, y1, point[7], 0, 2 * Math.PI);
        this.ctx.fillStyle = fillColor;    
        this.ctx.fill();
    }
    if (outerColor != null) {
        this.ctx.beginPath();
        this.ctx.arc(x1, y1, point[7], 0, 2 * Math.PI);
        this.ctx.lineWidth = point[8];
        this.ctx.strokeStyle = outerColor;    
        this.ctx.stroke();
    }
    
    if (point[2] != "") {
//        this.drawText2(x1+3, y1-10, point[2], color);
        this.drawText3(x1, y1, point[2], textColor, point[4], point[9]); // text, color, textSize, position 1 - 8
    }
}

//vykresleni cary mezi body [x1, y1] a [x2, y2]; opt. mozno zadat barvu a sirku cary
NPlayer.prototype.drawLine = function(x1, y1, x2, y2, color, width, dashed) {
    if (color == undefined) color = "#000";
    if (width == undefined) width = 1;
    if (dashed == undefined) dashed = false;
    
    this.ctx.strokeStyle = color;
    this.ctx.lineWidth = width;
    
    if (dashed) {
        
//        console.log("[" + x1 + ", " + y1 + "], [" + x2 + "," + y2 +"]");
        
        var dashDiagonalLength = 10;
        var dashSpace = 3;
        
        var actLength = dashDiagonalLength;
        
//        var fromX1 = x1;
//        var fromY1 = y1;
        var toX2 = 0;
        var toY2 = 0;
        var maxX = Math.max(x1, x2);
        
        var cdl = Math.sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1)); // celkova delka usecky
        var m = (y2 - y1) / (x2 - x1);
        
        var fromX1 = maxX;
        var fromY1 = m * (fromX1 - x1) + y1;
        
        while (actLength < cdl) {
            toX2 = maxX - ( (Math.abs(x2 - x1)) / cdl ) * actLength;
            toY2 = m * (toX2 - x1) + y1;
            this.ctx.beginPath()
            this.ctx.moveTo(fromX1, fromY1);
            this.ctx.lineTo(toX2, toY2);
            this.ctx.stroke();
//            console.log("[" + fromX1 + ", " + fromY1 + "], [" + toX2 + "," + toY2 +"]");
            actLength += dashSpace;
            fromX1 = maxX - ( (Math.abs(x2 - x1)) / cdl ) * actLength;
            fromY1 = m * (fromX1 - x1) + y1;
            
            actLength += dashDiagonalLength;
        }
    } else {
        
        this.ctx.beginPath()
        this.ctx.moveTo(x1, y1);
        this.ctx.lineTo(x2, y2);
        this.ctx.stroke();
    }
}

NPlayer.prototype.initTextArea = function() {
    if (this.displayTextArea) {
        this.textArea = document.createElement("div");
//    this.textArea.setAttribute("style", "width: " + this.width + "px;");
        this.textArea.setAttribute("class", "textArea");
        this.targetContainer.appendChild(this.textArea);
    }
}

NPlayer.prototype.getActTextID = function() {
    texts = this.data.text;
    for (var i = 0; i < texts.length; i++) {
        if (this.position >= texts[i].f && this.position <= texts[i].t){
            return i;
        }
    }
    return null;
}

NPlayer.prototype.actText = function() {
    if (this.displayTextArea) {
        var textAreaID = this.getActTextID();
        if (textAreaID != this.actTextID) {
            this.actTextID = textAreaID;
            this.actTextByID(textAreaID);
        }
    }
}

NPlayer.prototype.actTextByID = function(id) {
    if (id == null || id == undefined) {
        this.textArea.innerHTML = "";
    } else {
        this.textArea.innerHTML = this.data.text[id].txt;
        
        if (typeof MathJax != "undefined") {
            MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.textArea]);
        }
    }
}

NPlayer.prototype.drawText3 = function(x, y, text, color, size, position) {
    if (color == undefined) color = "#000";
    if (size == undefined) size = this.textSize;
    if (position == undefined) position = 1;
    
//    console.log(x, text);
    this.label = new textLabel(x,y,color,position);
    this.processLabel(this.label, text, size, 0); // posun ve smeru +y pro index
    this.drawLabel(this.label);
//    if (text.indexOf("_") == -1) {
//        this.ctx.fillStyle = color;
//        this.ctx.font = "italic " + size +"px serif";
//        this.ctx.fillText(text, x, y);
//        return this.ctx.measureText(text).width;
//    } else {
//        var preText = text.substring(0, text.indexOf("_"));
//        var widthOfText = this.drawText2(x, y, preText, color, size);
//        var indx = text.substring(text.indexOf("_") + 1, text.indexOf("_") + 2);
//        var widthOfIndex = this.drawText2(x+widthOfText, y+5, indx, color, size * 0.7);
//        if (text.length >= text.indexOf("_") + 2) {
//            var aftText = text.substring(text.indexOf("_") + 2);
//            this.drawText2(x + widthOfText + widthOfIndex, y, aftText, color, size);
//        }
//    }
    
}
NPlayer.prototype.processLabel = function(label, text, size, indexY) {
    if (text.length == 0) {
        return;
    }
    if (text.indexOf("_") == -1) {
        this.ctx.fillStyle = label.color;
        this.ctx.font = "italic " + size +"px serif";
        var width = this.ctx.measureText(text).width;
        
        var part = new textPart(label.width, indexY, text, size, width, size)
        label.width += width; // celkova sirka += sirka casti
        if (label.height == 0) {
            label.height = size;
        } else if (label.height < indexY + label.height) { // size -> label.height ???
            label.height = indexY + label.height;
        }
        label.parts.push(part);
        return;
    }
    else {
        var preText = text.substring(0, text.indexOf("_{")); // pred indexem
        if (preText.length > 0) {
//            console.log("pretext", preText);
            this.processLabel(label, preText, size, indexY);
            
//            var width = this.ctx.measureText(preText).width;
//            var part = new textPart(label.width, indexY, preText, size, width, size)
//            label.width += width; // celkova sirka += sirka casti
//            if (label.height < indexY + size) {
//                label.height = indexY + size;
//            }
        }
        var inStartIndex = text.indexOf("_{") + 2;
        var inEndIndex = text.indexOf("_{") + 2 + $findClosingIndex(text.substr(text.indexOf("_{") + 2), 1, "_{" , "}");
        var inText = text.substring(inStartIndex, inEndIndex);
        if (inText.length > 0) {
//            console.log("intext", inText);
            this.processLabel(label, inText, size * this.textIndexCoeficient, indexY + size * this.textYposCoeficient);
        }
        var afterText = text.substr(inEndIndex + 1);
        if (afterText.length > 0) {
//            console.log("aftertext", afterText);
            this.processLabel(label, afterText, size, indexY);
        }
    }
}
NPlayer.prototype.drawLabel = function(label) {
//    console.log(label);
    switch(label.position) {
        case 1:
            label.x += this.textSpace;
            label.y -= this.textSpace;
            break;
        case 2:
            label.x += this.textSpace;
            label.y += label.height / 2;
            break;
        case 3:
            label.x += this.textSpace;
            label.y += this.textSpace + label.height;
            break;
        case 4:
            label.x -= label.width / 2;
            label.y += this.textSpace + label.height;
            break;
        case 5:
            label.x -= (this.textSpace + label.width);
            label.y += this.textSpace + label.height;
            break;
        case 6:
            label.x -= (this.textSpace + label.width);
            label.y += label.height / 2;
            break;
        case 7:
            label.x -= (this.textSpace + label.width);
            label.y -= this.textSpace;
            break;
        case 8:
            label.x -= label.width / 2;
            label.y -= this.textSpace;
            break;
    }
    var part;
    for (var i = 0; i < label.parts.length; i++) {
        part = label.parts[i];
        this.drawPart(part.text, label.x+part.x, label.y+part.y, label.color, part.size);
    }
}

NPlayer.prototype.drawPart = function(text,x,y,color,size) {
    this.ctx.fillStyle = color;
    this.ctx.font = "italic " + size +"px serif";
    this.ctx.fillText(text, x, y);
}

textLabel = function(x,y,color,position) {
    this.x = x;
    this.y = y;
    this.color = color;
    this.position = position;
    this.width = 0;
    this.height = 0;
    this.parts = [];
}

// class; x,y relative to parent
textPart = function(x,y,text,size,width,height) {
    this.x = x;
    this.y = y;
    this.text = text;
    this.size = size;
    this.width = width;
    this.height = height;
}

// p_{10_{5}}
function $findClosingIndex(text, opened, openSequence, searchSequence) {
    var opSeq = opened; // how many opened seq
    var stub, searchSeq, openSeq, lastClosing;
    for (var i = 0; i < text.length; ) {
        lastClosing = i;
        stub = text.substr(i);
        searchSeq = stub.indexOf(searchSequence);
        openSeq = stub.indexOf(openSequence);
//        console.log(stub, opSeq, searchSeq, openSeq, i);
        
        if (searchSeq == -1) {
            return i;
        }
        
        if (openSeq > -1 && openSeq < searchSeq) { //_{}
            opSeq++;
            i += openSeq + openSequence.length;
        } else { // xxx}_{
            opSeq--;
            i += searchSeq + searchSequence.length;
        }
        
        if (opSeq == 0) {
            return i - searchSequence.length;
        }
    }
}

var calcDistanceToMove = function(delta, speed) {
     return (speed * delta) / 1000;
 }