import V2 from "./V2.js";

/**
 * Clase auxiliar para dibujar figuras 2D usando Contexto2D.
 * @author Melissa Méndez Servín.
 */
class Context2DUtils{
    constructor(){
        this.ctx = canvas.getContext('2d');
    }
    clear(){
        this.ctx.clearRect(0, 0, canvas.width, canvas.height);
    }
    setOrigin(pos){
        this.ctx.translate(pos.x, pos.y);
    }
    putText(text,size, xPos, yPos, color){
        this.ctx.fillStyle = color;
        this.ctx.font = size + "px Catamaran";
        this.ctx.fillText(text, xPos, yPos);
        this.ctx.fillStyle = "black";
    }
    /**
     * Dibuja un camino dada una serie de puntos.
     */
    drawLines(points, color, size){
        this.ctx.beginPath();
        this.ctx.lineWidth = size;
        this.ctx.strokeStyle = color || "black";
        for(let i = 1; i < points.length; i++){
            this.ctx.moveTo(points[i-1].x, points[i-1].y);
            this.ctx.lineTo(points[i].x, points[i].y);
        }
        this.ctx.stroke();
        this.ctx.strokeStyle = "black";
    }
    /**
     * Dibuja una linea dados los puntos p1 y p2 recibidos en el canvas.
     */
    drawLine(p1, p2, size=1, color){
        this.ctx.beginPath();
        this.ctx.lineWidth = size;
        this.ctx.strokeStyle = color || "black";
        this.ctx.moveTo(p1.x, p1.y);
        this.ctx.lineTo(p2.x, p2.y);
        this.ctx.stroke();
        this.ctx.strokeStyle = "black";
    }
    /**
     * Dibuja una circunferencia en el canvas.
     */
    drawCircle(origin,r,color,size=1,fill){
        this.ctx.beginPath();
        this.ctx.strokeStyle = color;
        this.ctx.lineWidth = size;
        this.ctx.arc(origin.x, origin.y,r, 0, 2*Math.PI);
        if(fill){
            this.ctx.fillStyle = fill;
            this.ctx.fill();
            this.ctx.fillStyle = "black";
        }
        this.ctx.stroke();
    }
    drawRect(origin, width, height, color, size){
        this.ctx.lineWidth = size || 1;
        this.ctx.strokeStyle = color;
        this.ctx.strokeRect(origin.x, origin.y , width, height);
    }
    drawFillRect(origin, width, height, color){
        this.ctx.fillStyle = color;
        this.ctx.fillRect(origin.x, origin.y , width, height);
    }
    drawUpArrow(p1,p2,color, size, label){
        this.drawLine(p1,p2,size,color); 

        this.ctx.beginPath();
        this.ctx.lineWidth =  size || 1;
        this.ctx.strokeStyle = color;
        this.ctx.moveTo(p2.x - 3, p2.y + 7);
        this.ctx.lineTo(p2.x, p2.y);
        this.ctx.lineTo(p2.x + 3, p2.y + 7);
        this.ctx.stroke();

        this.putText(label, 14, p2.x - 3, p2.y - 8);
    }
    drawRightArrow(p1,p2, size, color, label){
        this.drawLine(p1,p2,size,color); 

        this.ctx.beginPath();
        this.ctx.lineWidth =  size || 1;
        this.ctx.strokeStyle = color;
        this.ctx.moveTo(p2.x - 7, p2.y - 3);
        this.ctx.lineTo(p2.x, p2.y);
        this.ctx.lineTo(p2.x - 7, p2.y + 3);
        this.ctx.stroke();

        this.putText(label, 14, p2.x + 3, p2.y + 4);
    }
    drawTriangle(p1, p2, p3, size, color, fill){
        this.ctx.beginPath();
        this.ctx.lineWidth =  size || 1;
        this.ctx.strokeStyle = color;
        this.ctx.moveTo(p1.x, p1.y);
        this.ctx.lineTo(p2.x, p2.y);
        this.ctx.lineTo(p3.x, p3.y);
        this.ctx.closePath();
        if(fill){
            this.ctx.fillStyle = fill;
            this.ctx.fill();
            this.ctx.fillStyle = "black";
        }
        this.ctx.stroke();
        this.ctx.strokeStyle = "black";

    }
    drawDistanceH(p1, p2, label, size, color){
        this.drawLine(p1, p2,size,color); 
        this.drawLine(p1.movePoints(0,-3), p1.movePoints(0,3),size,color); 
        this.drawLine(p2.movePoints(0,-3), p2.movePoints(0,3),size,color); 
        var d = p1.x + (p2.x - p1.x)/2; 
        this.putText(label, 14, d, p2.y - 4);
    }
    drawDistanceV(p1, p2, label, size, color){
        this.drawLine(p1, p2,size,color); 
        this.drawLine(p1.movePoints(-3,0), p1.movePoints(3,0),size,color); 
        this.drawLine(p2.movePoints(-3,0), p2.movePoints(3,0),size,color); 
        var d = p1.y + (p2.y - p1.y)/2; 
        this.putText(label, 14, p1.x - 12, d);
    }
    drawDashLine(p1, p2, pattern, size=1, color){
        this.ctx.beginPath();
        this.ctx.lineWidth = size;
        this.ctx.strokeStyle = color;
        this.ctx.setLineDash(pattern);
        this.ctx.moveTo(p1.x, p1.y);
        this.ctx.lineTo(p2.x, p2.y);
        this.ctx.stroke();
        this.ctx.setLineDash([]);
        this.ctx.strokeStyle = "black";
    }
    drawArc(center, r, sAngle, eAngle, color, size, counterclockwise=false){
        this.ctx.beginPath();
        this.ctx.strokeStyle = color || "black";
        this.ctx.lineWidth = size;
        this.ctx.arc(center.x, center.y, r, sAngle, eAngle, counterclockwise);
        this.ctx.stroke();
    }
    drawFillArc(center, r, sAngle, eAngle, counterclockwise, fillColor){
        this.ctx.beginPath();
        this.ctx.moveTo(center.x,center.y);
        this.ctx.arc(center.x, center.y, r, sAngle, eAngle, counterclockwise);
        this.ctx.closePath();
        this.ctx.fillStyle = fillColor;
        this.ctx.fill();
    }
    
    drawAxes(axes,size=2, color){  
        color = color || "black";
        //Eje X origen   
        this.drawRightArrow({x:axes.x.x, y: axes.origin.y},{x:axes.x.y, y: axes.origin.y},size,color,'x');
        //Eje Y origen
        this.drawUpArrow({y:axes.y.x, x: axes.origin.x}, {y:axes.y.y, x: axes.origin.x}, size,color,'y');
    }
    /**
     * Calcula la interpolación lineal entre dos puntos p0 y p1, 
     * dado t, i.e, lerp(t) = (1-t) * p0 + p1 * t.
     * @param {V2} p0 el primer punto.
     * @param {V2} p1 el segundo punto.
     * @param {Float} t parámetro de tiempo para determinar la posición entre 
     *                  los dos puntos. 
     * @return {V2} el punto sobre la línea p0p1.
     */
    lerp(p0,p1,t){
        return p0.scale(1-t).add(p1.scale(t));
    }
    /**
     * Calcula el punto sobre la curva Bézier cuadrática definida por los puntos
     * de control p0,p1 y p2 en función de t.
     * @param {Vector3} p0 el primer punto de control.
     * @param {Vector3} p1 el segundo punto de control.
     * @param {Vector3} p2 el tercer y último punto de control.
     * @param {Float} t parámetro de tiempo para determinar la posición.
     * @return {Vector3} el punto sobre la curva.
     */
    bezierC2(p0,p1,p2,t){
        let l0 = this.lerp(p0,p1,t);
        let l1 = this.lerp(p1,p2,t);
        return this.lerp(l0,l1,t);
    }

    /**
     * Calcula el punto sobre la curva Bézier cúbica definida por los puntos
     * de control p0,p1,p2 y p3 en función de t.
     * @param {Vector3} p0 el primer punto de control.
     * @param {Vector3} p1 el segundo punto de control.
     * @param {Vector3} p2 el tercer y último punto de control.
     * @param {Float} t parámetro de tiempo para determinar la posición.
     * @return {Vector3} el punto sobre la curva.
     */
     bezierC3(p0,p1,p2,p3,t){
        let l0 = this.lerp(p0,p1,t);
        let l1 = this.lerp(p1,p2,t);
        let l2 = this.lerp(p2,p3,t);
        let r0 = this.lerp(l0, l1, t);
        let r1 = this.lerp(l1, l2, t);
        return this.lerp(r0,r1,t);
    }

    /**
     * Calcula el punto sobre la curva Bézier de grado ponints.length-1 definida por el 
     * conjunto de puntos de control dado en función de t.
     * @param {Vector3[]} points el conjunto de puntos de control.rol.
     * @param {Float} t parámetro de tiempo para determinar la posición.
     * @return {Vector3} el punto sobre la curva.
     */
    bezierCurve(points, t){
        if(points.length == 1)
            return points[0];
        let set = [];
        for(var i = 1; i < points.length; i++){
            set.push(this.lerp(points[i-1],points[i],t));
        }
        return this.bezierCurve(set,t);
    }
    drawSubBezierAndGetPoints(control_points, t, set_of_points=[], point_size, line_size, color){
        if(control_points.length == 1)
            return set_of_points;
        let new_control_points = [];
        for(var i = 1; i < control_points.length; i++){
            let lerp = this.lerp(control_points[i-1],control_points[i],t);
            new_control_points.push(lerp);
            set_of_points.push(lerp);
            this.drawCircle(lerp, point_size, color, 0, color);
        }
        this.drawLines(new_control_points, color, line_size);
        return this.drawSubBezierAndGetPoints(new_control_points,t, set_of_points, point_size, line_size, color);
    }
    /**
     * Grafica la curva Bézier de grado ponints.length-1 definida por el 
     * conjunto de puntos de control dado hasta t.
     * @param {Vector3[]} points el conjunto de puntos de control.rol.
     * @param {Float} t parámetro de tiempo para determinar la posición.
     * @return {Vector3} el punto sobre la curva.
     */
    drawBezierCurve(points, t, numPoints,color,size){
        if(points.length == 1) return;
        const ratio = 1/numPoints;
        var prev = this.bezierCurve(points,0);
        var curr;
        this.ctx.beginPath();
        this.ctx.lineWidth = size;
        this.ctx.strokeStyle = color || "black";
        for(var i = ratio; i< t; i+= ratio){
            curr = this.bezierCurve(points,i);
            this.ctx.moveTo(prev.x, prev.y);
            this.ctx.lineTo(curr.x, curr.y);
            prev = curr;
        }
        curr = this.bezierCurve(points, t);
        this.ctx.moveTo(prev.x, prev.y);
        this.ctx.lineTo(curr.x, curr.y);

        this.ctx.stroke();
        this.ctx.strokeStyle = "black";
    }
    drawQuadBezierT(p0, p1, p2, t, numPoints, color, size){
        if (t == 0) return;
        const ratio = t/numPoints;
        var prev = this.bezierC2(p0,p1,p2,0);
        var curr;
        this.ctx.beginPath();
        this.ctx.lineWidth = size;
        this.ctx.strokeStyle = color || "black";
        var i = ratio;
        while(i < t){
            curr = this.bezierC2(p0,p1,p2,i);
            this.ctx.moveTo(prev.x, prev.y);
            this.ctx.lineTo(curr.x, curr.y);
            prev = curr;
            i += ratio;
        }
        curr = this.bezierC2(p0,p1,p2,t);
        this.ctx.moveTo(prev.x, prev.y);
        this.ctx.lineTo(curr.x, curr.y);

        this.ctx.stroke();
        this.ctx.strokeStyle = "black";
    }
    drawCubicBezierT(p0, p1, p2, p3, t, numPoints, color, size){
        if (t == 0) return;
        const ratio = t/numPoints;
        var prev = this.bezierC3(p0,p1,p2, p3, 0);
        var curr;
        this.ctx.beginPath();
        this.ctx.lineWidth = size;
        this.ctx.strokeStyle = color || "black";
        var i = ratio;
        while(i < t){
            curr = this.bezierC3(p0,p1,p2,p3,i);
            this.ctx.moveTo(prev.x, prev.y);
            this.ctx.lineTo(curr.x, curr.y);
            prev = curr;
            i += ratio;
        }
        curr = this.bezierC3(p0,p1,p2,p3,t);
        this.ctx.moveTo(prev.x, prev.y);
        this.ctx.lineTo(curr.x, curr.y);

        this.ctx.stroke();
        this.ctx.strokeStyle = "black";
    }

    drawArrow(p0, p1, size, color) {
        var headlen = 15; // length of head in pixels
        var dx = p1.x - p0.x;
        var dy = p1.y - p0.y;
        var angle = Math.atan2(dy, dx);
        this.ctx.beginPath();
        this.ctx.lineWidth = size;
        this.ctx.strokeStyle = color || "black";this.ctx.moveTo(p0.x, p0.y);
        this.ctx.lineTo(p1.x, p1.y);
        this.ctx.lineTo(p1.x - headlen * Math.cos(angle - Math.PI / 6), p1.y - headlen * Math.sin(angle - Math.PI / 6));
        this.ctx.moveTo(p1.x, p1.y);
        this.ctx.lineTo(p1.x - headlen * Math.cos(angle + Math.PI / 6), p1.y - headlen * Math.sin(angle + Math.PI / 6));  
        this.ctx.stroke();
        this.ctx.strokeStyle = "black";
    }
}
let CtxUtils = new Context2DUtils();
export default CtxUtils;