import M3 from "./M3.js";
import M4 from "./M4.js";
import V2 from "./V2.js";

/**
 * Clase auxiliar para realizar diversos cálculos matemáticos.
 * @author Melissa Méndez Servín.
 */

/**
 * Devuelve un número random dentro de un rángo [min,max].
 * @param {*} min 
 * @param {*} max 
 * @returns 
 */
function randomIntR(min, max){
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min) + min);
}
function randomInt(max){
    max = Math.floor(max);
    return Math.floor(Math.random() * max);
}
function round(number){
    return Number(Math.round(number.toFixed(2) + 'e' + 2) + 'e-' + 2);
}
function sin(x) {
    return Math.sin(x) + 8 - 8;
}
function cos(x) {
    return Math.cos(x) + 8 - 8;
}
function cosDegrees(x) {
    return Math.cos(degreesToRadians(x)) + 8 - 8;
}
function radiansToDegrees(r){
    return r * 180/Math.PI;
}
function degreesToRadians(d){
    return d * Math.PI/180;
}
function atan(co,ca){
    if(co == 0 && ca == 0) return 0;
    return Math.atan(co / ca) * (180 / Math.PI);
}
function lineIntersection(sP1, d1, sP2, d2){
    const line1 = getLineEquation(sP1, d1);
    const line2 = getLineEquation(sP2, d2);
    const denom = line1.a - line2.a - 1 + 1;
    const x = (denom == 0) ? 0 : (line2.c - line1.c)/denom;
    const y = (line1.a * x) + line1.c;
    return new V2(x,y);
}
function getLineEquation(sPoint, dir){
    const A = dir.y;
    const B = (dir.x == 0) ? 1 : -dir.x;
    const C = (sPoint.y * dir.x) - (sPoint.x * dir.y);
    return {a : -A/B + 1 - 1, c : -C/B + 1 - 1};
}
/**
 * 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.
 */
function 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.
     */
function bezierC3(p0,p1,p2,t){
    let l0 = lerp(p0,p1,t);
    let l1 = lerp(p1,p2,t);
    return lerp(l0,l1,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.
 */
function bezierCurve(points, t){
    if(points.length == 1)
        return points[0];
    let set = [];
    for(var i = 1; i < points.length; i++){
        set.push(lerp(points[i-1],points[i],t));
    }
    return bezierCurve(set,t);
}
/**
 * Obtiene las coordenadas en pantalla aproximadas a la posición del punto 
 * dado en el canvas (clipping coordinates). Considerando que la ventana y el 
 * canvas tienen las mismas medidas
 * [-1,1] = [0,width] y [-1,1] = [0,height]
 * (glCoords + 1) * 0.5 => [0,1]
 * @param {*} point el punto en el canvas 
 * @param {*} projectioMatrix  la matriz de proyección que se usa
 * @param {*} width  el ancho del canvas/ventana
 * @param {*} height la altura del canvas/ventana
 */
function getPixelPosition(point, projectioMatrix, width, height, unit){
    var position = [unit * point.x, unit * point.y, 1];
    var NDCCoords = M3.multiplyVector(projectioMatrix, position);
    var coords = { x: Math.floor((NDCCoords[0] + 1) * 0.5 * width), 
                   y: height - Math.floor((NDCCoords[1] + 1) * 0.5 * height)};
    return coords;
}
function getPixelPosition2(point, projectioMatrix, width, height, unit, sPoint=new V2(0,0)) {
    var position = [unit * (sPoint.x + point.x), unit * (sPoint.y + point.y), 1];
    var NDCCoords = M3.multiplyVector(projectioMatrix, position);
    var coords = { x: Math.floor((NDCCoords[0] + 1) * 0.5 * width), 
                   y: height - Math.floor((NDCCoords[1] + 1) * 0.5 * height)};
    return coords;
}
function getVectorLabelPosition(point, projectionMatrix, width, height, unit, diff, scale=.6){
    let midPoint = point.scale(scale);
    let coord = getPixelPosition(midPoint, projectionMatrix, width, height, unit);
    if (!diff)
        diff = {t : -2, l: -25};
    return {top : coord.y + diff.t, left: coord.x + diff.l, type: 'px'};
}
/**
 * 
 * @param {*} projectionMatrix 
 * @param {*} width 
 * @param {*} height 
 * @param {*} unit 
 * @param {*} vector 
 * @param {*} sPoint 
 */
function getVectorLabelEndPosition(projectionMatrix, width, height, unit, vector, sPoint=new V2(0,0)){
    var offset = 22/vector.length()/unit;
    var labelVector = vector.scale(1 + offset).add(sPoint);
    let coord = getPixelPosition(labelVector, projectionMatrix, width, height, unit);
    let diff = {x : -20 + (1 - (1/coord.x*1000)), y: -20 - 20* coord.y/height};
    return { top: coord.y + diff.y, left: coord.x + diff.x, type: 'px'};
}
/**
 * Devuelve el constructor con la posición de la etiqueta, de acuerdo 
 * al vector dado, y en su caso, punto inicia. La etiqueta estará posicionada
 * a la mitad del vector y rotada unos grados menos, i.e, a la derecha del vector. 
 * @param {*} projectionMatrix 
 * @param {*} width 
 * @param {*} height 
 * @param {*} unit 
 * @param {*} vector 
 * @param {*} sPoint 
 */
function getVectorLabelMidPosition(projectionMatrix, width, height, unit, vector, sPoint=new V2(0,0)){
    var offset = 1/vector.length();
    const angle = degreesToRadians(50 * offset);
    var rotatedPoint = new V2(vector.x * cos(angle) + vector.y * sin(angle), -(vector.x * sin(angle)) + vector.y * cos(angle));
    var labelVector = rotatedPoint.scale(.5).add(sPoint);
    let coord = getPixelPosition(labelVector, projectionMatrix, width, height, unit);
    let diff = {x : -20, y: -28};
    return { top: coord.y + diff.y, left: coord.x + diff.x, type: 'px'};
}

function getVectorLabelMidLPosition(projectionMatrix, width, height, unit, vector, sPoint=new V2(0,0)){
    var offset = 1/vector.length();
    const angle = degreesToRadians(50 * offset);
    var rotatedPoint = new V2(vector.x * cos(angle) - vector.y * sin(angle), vector.x * sin(angle) + vector.y * cos(angle));
    var labelVector = rotatedPoint.scale(.5).add(sPoint);
    let coord = getPixelPosition(labelVector, projectionMatrix, width, height, unit);
    let diff = {x : -30, y: -28};
    return { top: coord.y + diff.y, left: coord.x + diff.x, type: 'px'};
}
function getPixel3DPosition(gl, PMV_Matrix, offset_x=5, offset_y=0){
    let width = gl.canvas.clientWidth;
    let height = gl.canvas.clientHeight
    var NDCCoords = M4.multiplyVector(PMV_Matrix, [0,0,0,1]);
    var coords = { left: (NDCCoords[0]/NDCCoords[3] * 0.5 + 0.5) * width + offset_x,
                   top: (NDCCoords[1]/NDCCoords[3] * -0.5 + 0.5) * height + offset_y,
                   type: "px"};
    return coords;
}
export{
    randomInt, randomIntR, round,
    cos, sin, atan, cosDegrees,
    lineIntersection,
    radiansToDegrees, degreesToRadians,
    lerp, bezierC3, bezierCurve,
    getPixelPosition, getPixelPosition2,
    getVectorLabelPosition, 
    getVectorLabelEndPosition,
    getVectorLabelMidPosition,
    getVectorLabelMidLPosition, 
    getPixel3DPosition
}
