import {Button, setElementPosition, MathBox, resizeContainer2D} from "../../../modules/HTMLUtils.js";
import {randomIntR} from "../../../modules/MathUtils.js";
import V2 from "../../../modules/V2.js";
import M3 from "../../../modules/M3.js";

/**
 * Proceso del cálculo de una matriz inversa. (Ejemplo de uso del determinante). 
 * @author Melissa Méndez Servín.
 */
window.addEventListener("load", main);

//Matrices
var matrixA = [ 1, 2, 5,
                1, 3, 8,
                1, 4, 9];
var adjA = getMatrix(M3.transpose(getArrayAdjointTMatrix(matrixA)));
var res = det(matrixA);
var A = getMatrix(matrixA);
var detA = getDet(matrixA);
var adjMinors = [[0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2], [3,3]];
const letA = "\\text{Sea } \\mathbf{A} = ";
const step1 = "1. \\text{Determinar si det}(\\mathbf{A}) \\not = 0."  
const sol1Det = "\\therefore \\text{ se puede calcular la inversa.}";
const sol2Det = "\\therefore \\text{ no se puede calcular la inversa.} \\\\ \\kern{3em} (\\text{da click en }otro)";
const step2 = "2. \\text{Calcular la adjunta de }\\mathbf{A}.";  
const inverseDef = "\\mathbf{A}^{-1} = \\dfrac{1}{\\text{det}(\\mathbf{A})} \\text{adj}\\mathbf{A}";

var step = 0;
var subStep = 0;
var max_substeps = [1, 8, 9, 2];
function main(){
    resizeContainer2D();

    let equations = document.getElementById("equations");
    let controls = document.getElementById("ui-container");
    controls.className = "vertical-bttns-box";

    //HTML CONTROLS
    const colorButton = "#9ACD32";
    let buttons = [ new Button(controls, upadateStep(-1), "Prev", "green-bg", true),
                    new Button(controls, upadateStep(1), "Sig", "green-bg"),
                    new Button(controls, reset, "Reset", "green-bg"),
                    new Button(controls, other, "Otro", "green-bg")];
    
    //MathBoxes para escribir las matrices
    const defaultPos = {top: -100, left: -100};
    const r2Pos = new V2(45, 150);
    const r2SolPos = r2Pos.movePoints(155,14);

    let r0 = new MathBox(equations, {top: 0, left: 15, type: "px"}, letA + A);
    let stepr1 = new MathBox(equations, {top: 100, left: 15, type: "px"}, step1);
    let r2 = new MathBox(equations, {top: r2Pos.y, left: r2Pos.x, type: "px"}, detA);
    let r2Sol = new MathBox(equations, {top: r2SolPos.y, left: r2SolPos.x, type: "px"});
    let r3Sol = new MathBox(equations, {top: r2SolPos.y + 80, left: r2SolPos.x, type: "px"});
    let r4Sol = new MathBox(equations, {top: r2SolPos.y + 140, left: r2SolPos.x, type: "px"});
    let r5Sol = new MathBox(equations, {top: r2SolPos.y + 190, left: r2SolPos.x, type: "px"});
    //HTML rectángulos
    let horizontal_area = document.getElementById("horizontal-area");
    let vertical_area = document.getElementById("vertical-area");    
    setElementPosition(horizontal_area, defaultPos);
    setElementPosition(vertical_area, defaultPos);
    
    //Para la animación
    var current = 0;
    var elapsed = 0;
    var lastTime = Date.now();
    let max_elapsed_wait = 2000/1000;
    var complete = true;

    let offsets_x1 = [ 2,  53, 100, 49, 73, 138, 162, 229, 253];
    let offsets_x2 = [ 50, 100, 150, -1000];
    let offsets_y2 = [ 4, 26, 50, -1000];
    let h_position;
    let v_position;
    
    setElementPosition(horizontal_area, defaultPos);
    setElementPosition(vertical_area, defaultPos);
    
    window.addEventListener('resize', draw);
    draw();

    function draw(){
        resizeContainer2D();
       
        if(complete)
            lastTime = Date.now();
        complete = clockWait();

        r0.setText(letA + A);
        r2.setText("");
        r2Sol.setText("");
        r3Sol.setText("");
        r4Sol.setText("");
        r5Sol.setText("");
        
        if(step == 0){
            stepr1.setText(step1);
            r2.setText(detA);
            setElementPosition(horizontal_area, defaultPos);
            setElementPosition(vertical_area, defaultPos);
        }
        if(step == 1)
            renderStep1();
        
        if(step > 1)
            r0.setText(letA + A + "\\kern{1.2em}\\text{y} \\kern{.7em} \\text{det}(\\mathbf{A}) \\not = 0.");

        if(step == 2){
            renderStep2A();
        }
        if(step == 3){
            renderStep2B();
        }
        if(step == 4){
            stepr1.setText("\\text{Entonces como} \\kern{.5em}" + inverseDef + "\\kern{.5em}\\text{tenemos que}");
            r3Sol.setPosition({top: r2Pos.y + 50, left: r2Pos.x + 60, type: "px"});
            let determinant = det(matrixA);
            let sign = (determinant < 0) ? "-" : "";
            r3Sol.setText("\\mathbf{A}^{-1} = " + sign + `\\dfrac{1}{${Math.abs(determinant)}}` + adjA); 
        }

        if(step > 0 & subStep < max_substeps[step])
            for(var i = 0; i < 4; i++)
                buttons[i].disabled(true);
        else{
            let max_step =  (res != 0) ? 4 : 1;
            buttons[0].updateState(step, 1, max_step);
            buttons[1].updateState(step, 0, max_step-1);
            buttons[2].updateState(step, 0, max_step);
            buttons[3].updateState(step, 0, max_step);
        }
    }   
    /**
    * false sigue esperando
    * true terminó la espera
    */
   function clockWait(){
       current = Date.now();
       elapsed = (current - lastTime) / 1000;
       if (elapsed > max_elapsed_wait) {
           elapsed = 0;
           lastTime = current;
           if(subStep < max_substeps[step]){
               subStep = (subStep + 1);
               window.requestAnimationFrame(draw);
           }
           return true;
       }
       window.requestAnimationFrame(draw);
       return false;
   }
   /**
    * Actualiza el paso en el que va.
    * @param {*} button 
    * @returns 
    */
    function upadateStep(button){
        return function(){
            var max_step =  (res != 0) ? 4 : 1;
            if (step + button < 0 || step + button > max_step)
                return;
            step += button;
            subStep = 0;
            lastTime = Date.now();
            draw(); 
        }
    }
    /**
     * Vuelve al primer paso.
     */
    function reset(){
        step = 0;
        buttons[0].updateState(step, 1, 6);
        buttons[1].updateState(step, 0, 6-1);
        subStep = 0;
        lastTime = Date.now();
        draw();
    }
    /**
     * Cambia de matriz.
     */
    function other(){
        matrixA = getPseudoRandomMatrix();
        A = getMatrix(matrixA);
        detA = getDet(matrixA);
        adjA = getMatrix(M3.transpose(getArrayAdjointTMatrix(matrixA)));
        res = det(matrixA);
        reset();
    }

    function renderStep1(){
        stepr1.setText(step1);
        r2.setText(detA);

        if(subStep < 3){
            h_position = { top: r2Pos.y, left: r2Pos.x + offsets_x1[0], type: "px"};//r0
            v_position = { top: r2Pos.y, left: r2Pos.x + offsets_x1[subStep], type: "px"};//ci
        }else{
            h_position = defaultPos;
            v_position = defaultPos;
        }
        setElementPosition(horizontal_area, h_position);
        setElementPosition(vertical_area, v_position);
        r3Sol.setPosition({top: r2SolPos.y + 80, left: r2SolPos.x, type: "px"});
        r2Sol.setText(getDetSolS0(matrixA));
        r3Sol.setText(getDetSolS1(matrixA));
        r4Sol.setText(getFinalDetSol(matrixA));
        if(subStep == 8)
            if(res != 0)
                r5Sol.setText(sol1Det);
            else
                r5Sol.setText(sol2Det);
    }
    function renderStep2A(){
        stepr1.setText(step2);
        r3Sol.setPosition({top: r2SolPos.y + 85, left: r2SolPos.x - 35, type: "px"});
        
        let minor = adjMinors[subStep];
        r2.setText(getAdjoint(matrixA, minor[0], minor[1]));
        r3Sol.setText(getAdjointMatrix(matrixA, minor[0], minor[1]));
        h_position = { top: r2Pos.y + offsets_y2[minor[0]], left: r2Pos.x + offsets_x2[0], type: "px"};//r0
        v_position = { top: r2Pos.y + offsets_y2[0], left: r2Pos.x + offsets_x2[minor[1]], type: "px"};//ci

        setElementPosition(horizontal_area, h_position);
        setElementPosition(vertical_area, v_position);
    }
    function renderStep2B(){
        stepr1.setText(step2);
        r3Sol.setPosition({top: r2SolPos.y + 210, left: r2SolPos.x - 108, type: "px"});
        
        r2.setText("\\text{adj} \\mathbf{A}" + getAdjointMatrix(matrixA, 3,3));
        let adjM = getArrayAdjointTMatrix(matrixA);
        let adjT = getMatrix(adjM) + "^{T}";
        adjA = getMatrix(M3.transpose(adjM));
        if(subStep == 1)
            r3Sol.setText(" = " + adjT);
        if(subStep == 2)
            r3Sol.setText(" = " + adjT + " = " + adjA);
       
        setElementPosition(horizontal_area, defaultPos);
        setElementPosition(vertical_area, defaultPos);
    }
}
function det(m){
    let det11 = m[0] * ((m[4]*m[8]) - (m[7]*m[5]));
    let det12 = -1 * m[3] * ((m[1]*m[8]) - (m[7]*m[2]));
    let det13 = m[6] * ((m[1]*m[5]) - (m[4]*m[2]));
    return (det11 + det12 + det13);
}
function getPseudoRandomMatrix(){
    let m = [];
    let not_inverse = randomIntR(-6,3);
    for(var i = 0; i<9; i++)
        m.push(randomIntR(-9,9));
    if(not_inverse >= 0){
       var vertical = randomIntR(-5,5);
       if(vertical < 0){
           m[3*not_inverse] = 0;
           m[3*not_inverse + 1] = 0;
           m[3*not_inverse + 2] = 0;
       }else{
            m[not_inverse] = 0;
            m[not_inverse + 3] = 0;
            m[not_inverse + 6] = 0;
       }
    }
    return m;
}
function getDetSolS0(m){
    var sol = " = \\kern{.6em}" + getSubDet11(m);
    if(subStep == 0)
        return sol;
    sol +=  getSubDet12(m);
    if(subStep == 1)
        return sol;
    sol += getSubDet13(m);
    return sol;   
}
function getDetSolS1(m){
    if(subStep < 3) return "";
    var tmp = (-1*m[7]*m[5] >= 0) ? "+" + m[7]*m[5] : "-" + Math.abs(m[7]*m[5]);
    var sol = " = \\kern{.6em}"  + `${m[0]}` + "(" + `${m[4]*m[8]}` + tmp + ")";
    if(subStep == 3)
        return sol;
    let m01 = (m[3] >= 0) ? "- " + `${m[3]}` : "+ " + `${Math.abs(m[3])}`;
    tmp = (-1*m[7]*m[2] >= 0) ? "+" + m[7]*m[2] : "-" + Math.abs(m[7]*m[2]);
    sol += m01 + "(" + `${m[1]*m[8]}` + tmp + ")"
    if(subStep == 4)
        return sol;
    let m02 = (m[6] >= 0) ? "+ " + `${m[6]}` : "- " + `${Math.abs(m[6])}`;
    tmp = (-1*m[4]*m[2] >= 0) ? "+" + m[4]*m[2] : "-" + Math.abs(m[4]*m[2]);
    sol += m02 + "(" + `${m[1]*m[5]}` + tmp + ")";
    return sol;   
} 
function getFinalDetSol(m){
    if(subStep < max_substeps[step] -1)
        return "";
    let res = det(m);
    return " = \\kern{.6em}" + res + ".";
}
function getSubDet11(m){
    return `${m[0]}` + getMinor11(m);
}
function getSubDet12(m){ //Impar
    let m12 = (-1*m[3] >= 0) ? "+ " + `${-1*m[3]}` : "- " + `${Math.abs(-1*m[3])}`;
    return m12 + "\\begin{vmatrix}" +
    `${m[1]} & ${m[7]} \\\\` +
    `${m[2]} & ${m[8]} \\\\` +
    "\\end{vmatrix}";
}
function getSubDet13(m){
    let m13 = (m[6] >= 0) ? "+ " + `${m[6]}` : "- " + `${Math.abs(m[6])}`;
    return m13 + getMinor13(m);
}
// Menores sobre r1
function getMinor11(m){
    return  "\\kern{.95em}" + "\\begin{vmatrix}" +
            `${m[4]} & ${m[7]} \\\\` +
            `${m[5]} & ${m[8]} \\\\` +
            "\\end{vmatrix}";
}
function getMinor12(m){ //Impar
     return "-" + "\\begin{vmatrix}" +
            `${m[1]} & ${m[7]} \\\\` +
            `${m[2]} & ${m[8]} \\\\` +
            "\\end{vmatrix}";
}
function getMinor13(m){
     return "\\kern{.95em}" + "\\begin{vmatrix}" +
            `${m[1]} & ${m[4]} \\\\` +
            `${m[2]} & ${m[5]} \\\\` +
            "\\end{vmatrix}";
}
// Menores sobre r2
function getMinor21(m){ //Impar
    return  "-" + "\\begin{vmatrix}" +
            `${m[3]} & ${m[6]} \\\\` +
            `${m[5]} & ${m[8]} \\\\` +
            "\\end{vmatrix}";
}
function getMinor22(m){ 
    return  "\\kern{.95em}" + "\\begin{vmatrix}" +
            `${m[0]} & ${m[6]} \\\\` +
            `${m[2]} & ${m[8]} \\\\` +
            "\\end{vmatrix}";
}
function getMinor23(m){ //Impar
    return "-"+ "\\begin{vmatrix}" +
            `${m[0]} & ${m[3]} \\\\` +
            `${m[2]} & ${m[5]} \\\\` +
            "\\end{vmatrix}";
}
//Menores sobre r3
function getMinor31(m){ 
    return  "\\kern{.95em}" + "\\begin{vmatrix}" +
            `${m[3]} & ${m[6]} \\\\` +
            `${m[4]} & ${m[7]} \\\\` +
            "\\end{vmatrix}";
}
function getMinor32(m){ //Impar
    return "-" + "\\begin{vmatrix}" +
            `${m[0]} & ${m[6]} \\\\` +
            `${m[1]} & ${m[7]} \\\\` +
            "\\end{vmatrix}";
}
function getMinor33(m){ 
    return  "\\kern{.95em}" + "\\begin{vmatrix}" +
            `${m[0]} & ${m[3]} \\\\` +
            `${m[1]} & ${m[4]} \\\\` +
            "\\end{vmatrix}";
}

function getDet(m){
    return "\\begin{vmatrix}" +
            getElem(m[0]) + "&" + getElem(m[3]) + "&" + getElem(m[6]) + "\\kern{.7em}" + "\\\\" +
            getElem(m[1]) + "&" + getElem(m[4]) +  "&" + getElem(m[7]) + "\\kern{.7em}" + "\\\\" +
            getElem(m[2]) + "&" + getElem(m[5]) + "&" + getElem(m[8]) + "\\kern{.7em}" +
            "\\end{vmatrix}";
}
function getMatrix(m){
    return "\\begin{bmatrix}" +
            getElem(m[0]) + "&" + getElem(m[3]) + "&" + getElem(m[6]) + "\\kern{.7em}" + "\\\\" +
            getElem(m[1]) + "&" + getElem(m[4]) +  "&" + getElem(m[7]) + "\\kern{.7em}" + "\\\\" +
            getElem(m[2]) + "&" + getElem(m[5]) + "&" + getElem(m[8]) + "\\kern{.7em}" +
            "\\end{bmatrix}";
}
function getElem(ele){
    let sign = (ele >= 0) ? "\\kern{.8em}" : "-";
    return sign + Math.abs(ele);
}
function getAdjoint(m, index_i, index_j){
    var adj = "\\text{adj}\\Biggl(" + getMatrix(m) + "\\Biggr) = \\begin{bmatrix}";
    for(var i = 1; i < 4; i++){
        for(var j = 1; j < 4; j++){
            if(index_i+1 == i && index_j+1 == j)
                adj +=  "{\\color{#9ACD32}\\mathbf{C}(\\mathbf{A}_{" +  i + j + "})}";
            else
                adj +=  "\\mathbf{C}(\\mathbf{A}_{" +  i + j + "})";
            adj += (j == 3) ? "\\\\" : "&";
        }
    }
    adj += "\\end{bmatrix}^{T}";
    return adj;
}
function getAdjointMatrix(m, index_i, index_j){
    let m11 = getColorElem(getMinor11(m), 0, 0, index_i, index_j);
    let m12 = getColorElem(getMinor12(m), 0, 1, index_i, index_j);
    let m13 = getColorElem(getMinor13(m), 0, 2, index_i, index_j);
    let m21 = getColorElem(getMinor21(m), 1, 0, index_i, index_j);
    let m22 = getColorElem(getMinor22(m), 1, 1, index_i, index_j);
    let m23 = getColorElem(getMinor23(m), 1, 2, index_i, index_j);
    let m31 = getColorElem(getMinor31(m), 2, 0, index_i, index_j);
    let m32 = getColorElem(getMinor32(m), 2, 1, index_i, index_j);
    let m33 = getColorElem(getMinor33(m), 2, 2, index_i, index_j);

    var adj = " = \\begin{bmatrix}";
    adj += m11 + "&" + m12 + "&" + m13 + "\\\\\\\\";
    adj += m21 + "&" + m22 + "&" + m23 + "\\\\\\\\";
    adj += m31 + "&" + m32 + "&" + m33;
    adj += "\\end{bmatrix}^{T}";
    return adj;
}
function getColorElem(ele, i, j, index_i, index_j){
    if(i == index_i && j == index_j)
        return "{\\color{#9ACD32}" + ele + "}";
    if( i < index_i || j < index_j && i <= index_i )
       return ele;
    return "{\\color{#FFFFFF}" + ele + "}";
}
function getArrayAdjointTMatrix(m){
    let minor11 = ((m[4]*m[8]) - (m[7]*m[5]));
    let minor12 = -1 * ((m[1]*m[8]) - (m[7]*m[2]));
    let minor13 = ((m[1]*m[5]) - (m[4]*m[2]));

    let minor21 = -1 * ((m[3]*m[8]) - (m[6]*m[5]));
    let minor22 = ((m[0]*m[8]) - (m[6]*m[2]));
    let minor23 = -1 * ((m[0]*m[5]) - (m[3]*m[2]));

    let minor31 = ((m[3]*m[7]) - (m[6]*m[4]));
    let minor32 = -1 * ((m[0]*m[7]) - (m[6]*m[1]));
    let minor33 = ((m[0]*m[4]) - (m[3]*m[1]));
    return [minor11, minor21, minor31,
            minor12, minor22, minor32,
            minor13, minor23, minor33];   
}