import { cSnTrapez, LinkedValue, Nub, ParallelStructure, ParallelStructureParams,
         ParamsSectionTrapez, ParamValueMode, SectionParametree, Session } from "../../src/index";
import { RegimeUniforme } from "../../src/regime_uniforme";
import { cSnCirc, ParamsSectionCirc } from "../../src/section/section_circulaire";
import { Cloisons } from "../../src/structure/cloisons";
import { CloisonsParams } from "../../src/structure/cloisons_params";
import { Dever, DeverParams } from "../../src/structure/dever";
import { CreateStructure } from "../../src/structure/factory_structure";
import { LoiDebit } from "../../src/structure/structure_props";

/**
 * IMPORTANT !
 * Décommenter temporairement la ligne suivante (import { } from "./mock_jasmine")
 * Pour exécuter ce code dans le débugger.
 * Faire de même avec le fichier test_func.ts
 */
// import { describe, expect, it, xdescribe, xit } from "../mock_jasmine";

let nub1: RegimeUniforme;
let prm1: ParamsSectionCirc;
let nub2: ParallelStructure;
let prm2: ParallelStructureParams;
let nub3: Cloisons;
let prm3: CloisonsParams;
let nub4: Dever;
let prm4: DeverParams;
let nub5: Dever;
let prm5: DeverParams;
let nub6: SectionParametree;
let prm6: ParamsSectionTrapez;

/**
 * crée l'environnement de test.
 * répété à chaque test car il manque un mock de beforeEach
 */
function createEnv() {
    // Nub 1 : Régime Uniforme
    const paramSect = new ParamsSectionCirc(2, 0.6613, 40, 1.2, 0.001, 1);
    paramSect.Pr.v = 0.01;
    const sect = new cSnCirc(paramSect);
    nub1 = new RegimeUniforme(sect);
    prm1 = nub1.section.prms as ParamsSectionCirc;

    // Nub 2 : Lois d'ouvrages
    prm2 = new ParallelStructureParams(0.5, 102, 101.5);
    prm2.Pr.v = 0.01;
    nub2 = new ParallelStructure(prm2);
    nub2.addChild(
        CreateStructure(
            LoiDebit.Cunge80,
            nub2
        )
    );
    nub2.addChild(
        CreateStructure(
            LoiDebit.TriangularWeirFree,
            nub2
        )
    );

    // Nub 3 : Passe à Bassin : Cloisons
    prm3 = new CloisonsParams(1.5, 102, 10, 1, 1, 0.5);
    nub3 = new Cloisons(prm3);
    nub3.addChild(
        CreateStructure(
            LoiDebit.OrificeSubmerged,
            nub3
        )
    );
    nub3.addChild(
        CreateStructure(
            LoiDebit.KIVI,
            nub3
        )
    );

    // Nub 4 : Lois de déversoirs Dénoyés
    prm4 = new DeverParams(0.5, 102, 10, 99);
    prm4.Pr.v = 0.01;
    nub4 = new Dever(prm4);
    nub4.addChild(
        CreateStructure(
            LoiDebit.WeirFree,
            nub4,
            false
        )
    );
    nub4.addChild(
        CreateStructure(
            LoiDebit.TriangularTruncWeirFree,
            nub4,
            false
        )
    );

    // Nub 5 : Lois de déversoirs Dénoyés (2)
    prm5 = new DeverParams(0.5, 102, 10, 99);
    prm5.Pr.v = 0.01;
    nub5 = new Dever(prm5);
    nub5.addChild(
        CreateStructure(
            LoiDebit.WeirFree,
            nub5,
            false
        )
    );
    nub5.addChild(
        CreateStructure(
            LoiDebit.TriangularTruncWeirFree,
            nub5,
            false
        )
    );

    // Nub 6 : Section Paramétrée
    prm6 = new ParamsSectionTrapez(1, 0.5, 1, 0.01, 1, 0.01, 2);
    prm6.Pr.v = 0.01;
    nub6 = new SectionParametree(new cSnTrapez(prm6));
    const prm7 = nub6.prms as ParamsSectionTrapez;

    // populate Session (for links)
    Session.getInstance().clear();
    Session.getInstance().registerNub(nub1);
    Session.getInstance().registerNub(nub2);
    Session.getInstance().registerNub(nub3);
    Session.getInstance().registerNub(nub4);
    Session.getInstance().registerNub(nub5);
    Session.getInstance().registerNub(nub6);
}

/**
 * Check that nub has :
 *  - exactly 1 parameter in CALC mode
 *  - a defined calculatedParam
 *  - at most 1 parameter in MINMAX / LISTE mode
 *  - a valid result after calculation
 */
function checkConsistency(nubToCheck: Nub) {
    expect(nubToCheck.calculatedParam).toBeDefined();
    let calcCount = 0;
    let varCount = 0;
    for (const p of nubToCheck.parameterIterator) {
        if (p.valueMode === ParamValueMode.CALCUL) {
            calcCount++;
        }
        if (p.valueMode === ParamValueMode.MINMAX || p.valueMode === ParamValueMode.LISTE) {
            varCount++;
        }
    }
    expect(calcCount).toBe(1);
    expect(varCount).toBeLessThanOrEqual(1);

    nubToCheck.CalcSerie();
    expect(nubToCheck.result).toBeDefined();
}

/**
 * Tests multiple modes permutations and checks consistency
 * after each modification
 */
function testModesPermutations(nubToTest: Nub) {

    // set every parameter to CALC mode
    for (const p of nubToTest.parameterIterator) {
        if (p.symbol === "Pr" || ! p.visible) {
            continue;
        }
        p.setCalculated();
        checkConsistency(nubToTest);
    }

    // set every parameter to MINMAX / LISTE mode
    let i = 0;
    for (const p of nubToTest.parameterIterator) {
        if (p.symbol === "Pr" || ! p.visible) {
            continue;
        }
        if (i % 2 === 0) {
            p.setValues(1, 5, 0.5); // sets valueMode to MINMAX
        } else {
            p.setValues([ 1, 2, 3, 4, 5 ]); // sets valueMode to LISTE
        }
        checkConsistency(nubToTest);
        i++;
    }

    // set every parameter to CALC then to SINGLE mode
    for (const p of nubToTest.parameterIterator) {
        if (p.symbol === "Pr" || ! p.visible) {
            continue;
        }
        p.setCalculated();
        checkConsistency(nubToTest);
        p.valueMode = ParamValueMode.SINGLE;
        checkConsistency(nubToTest);
    }

    // set every parameter to LINK mode then to SINGLE mode
    for (const p of nubToTest.parameterIterator) {
        if (p.symbol === "Pr" || ! p.visible) {
            continue;
        }
        const lv: LinkedValue[] = Session.getInstance().getLinkableValues(p);
        if (lv.length > 0) {
            p.defineReference(lv[0].nub, lv[0].symbol); // sets mode to LINK
            checkConsistency(nubToTest);
            p.valueMode = ParamValueMode.SINGLE;
            checkConsistency(nubToTest);
        }
    }

    // set every parameter to a random mode, except LINK
    /* for (const p of nubToTest.parameterIterator) {
        if (p.symbol === "Pr" || ! p.visible) {
            continue;
        }
        const r = Math.floor(Math.random() * 4);
        switch (r) {
            case 0:
                p.valueMode = ParamValueMode.SINGLE;
                break;
            case 1:
                p.setValues(1, 5, 0.5); // sets valueMode to MINMAX
                break;
            case 2:
                p.setValues([ 1, 2, 3, 4, 5 ]); // sets valueMode to LISTE
                break;
            case 3:
                p.setCalculated();
                break;
        }
        checkConsistency(nubToTest);
    } */
}

describe("cohérence des modes de paramètres : ", () => {

    it("varier un paramètre d'une Section Paramétrée", () => {
        createEnv();
        prm6.YB.setValues(0.5, 2, 0.075);
        /* for (const p of nub6.parameterIterator) {
            console.log(">>> param 2", p.symbol, ParamValueMode[p.valueMode]);
        } */
        nub6.CalcSerie(1, "Yf");
    });

    it("paramètre simple", () => {
        createEnv();
        testModesPermutations(nub1);
    });

    it("ouvrages en parallèle : Lois d'Ouvrages", () => {
        createEnv();
        testModesPermutations(nub2);
    });

    // @TODO reenable after fixing jalhyd#74
    xit("ouvrages en parallèle : Cloisons", () => {
        createEnv();
        testModesPermutations(nub3);
    });

    it("ouvrages en parallèle : Déversoirs", () => {
        createEnv();
        testModesPermutations(nub4);
    });

    it("paramètre varié lié", () => {
        createEnv();
        // vary Q on nub1
        prm1.Q.setValues(0.6, 2.4, 0.09);

        // link other Nubs Q to nub1.Q
        for (const n of [ nub2, nub3, nub4 ]) {
            n.prms.Q.defineReference(nub1.section, "Q");
            // set every parameter to MINMAX / LISTE mode
            let i = 0;
            for (const p of n.parameterIterator) {
                if (p.symbol !== "Q") {
                    if (i % 2 === 0) {
                        p.setValues(1, 5, 0.5); // sets valueMode to MINMAX
                    } else {
                        p.setValues([ 1, 2, 3, 4, 5 ]); // sets valueMode to LISTE
                    }
                }
                checkConsistency(n);
                i++;
            }
        }
    });

    it("paramètre varié lié indirectement", () => {
        createEnv();
        // vary Q on nub1
        prm1.Q.setValues(0.6, 2.4, 0.09);

        // link nub6.Q to nub1.Q
        prm6.Q.defineReference(nub1.section, "Q");

        // link other Nubs Q to nub6.Q
        for (const n of [ nub2, nub3, nub4 ]) {
            n.prms.Q.defineReference(nub6.section, "Q");
            // set every parameter to MINMAX / LISTE mode
            let i = 0;
            for (const p of n.parameterIterator) {
                if (p.symbol !== "Q") {
                    if (i % 2 === 0) {
                        p.setValues(1, 5, 0.5); // sets valueMode to MINMAX
                    } else {
                        p.setValues([ 1, 2, 3, 4, 5 ]); // sets valueMode to LISTE
                    }
                }
                checkConsistency(n);
                i++;
            }
        }
    });

    it("résultat varié lié", () => {
        createEnv();
        // vary LargeurBerge and compute Q on nub1
        prm1.LargeurBerge.setValues(1.25, 5, 0.188);
        prm1.Q.setCalculated();

        // link other Nubs Q to nub1.Q
        for (const n of [ nub2, nub3, nub4 ]) {
            n.prms.Q.defineReference(nub1.section, "Q");
            // set every parameter to MINMAX / LISTE mode
            let i = 0;
            for (const p of n.parameterIterator) {
                if (p.symbol !== "Q") {
                    if (i % 2 === 0) {
                        p.setValues(1, 5, 0.5); // sets valueMode to MINMAX
                    } else {
                        p.setValues([ 1, 2, 3, 4, 5 ]); // sets valueMode to LISTE
                    }
                }
                checkConsistency(n);
                i++;
            }
        }
    });

    it("résultat varié lié indirectement", () => {
        createEnv();
        // vary LargeurBerge and compute Q on nub1
        prm1.LargeurBerge.setValues(1.25, 5, 0.188);
        prm1.Q.setCalculated();

        // link nub6.Q to nub5.CvQT
        prm6.Q.defineReference(nub1.section, "Q");

        // link other Nubs Q to nub6.Q
        for (const n of [ nub2, nub3, nub4 ]) {
            n.prms.Q.defineReference(nub6.section, "Q");
            // set every parameter to MINMAX / LISTE mode
            let i = 0;
            for (const p of n.parameterIterator) {
                if (p.symbol !== "Q") {
                    if (i % 2 === 0) {
                        p.setValues(1, 5, 0.5); // sets valueMode to MINMAX
                    } else {
                        p.setValues([ 1, 2, 3, 4, 5 ]); // sets valueMode to LISTE
                    }
                }
                checkConsistency(n);
                i++;
            }
        }
    });

    it("résultat complémentaire varié lié", () => {
        createEnv();
        // vary LargeurBerge and compute Q on nub1
        prm5.Z1.setValues(50, 200, 10);
        prm5.Q.setCalculated();

        // link other Nubs Q to nub5.CvQT
        for (const n of [ nub2, nub3, nub4 ]) {
            n.prms.Q.defineReference(nub5, "CvQT");
            // set every parameter to MINMAX / LISTE mode
            let i = 0;
            for (const p of n.parameterIterator) {
                if (p.symbol !== "Q") {
                    if (i % 2 === 0) {
                        p.setValues(1, 5, 0.5); // sets valueMode to MINMAX
                    } else {
                        p.setValues([ 1, 2, 3, 4, 5 ]); // sets valueMode to LISTE
                    }
                }
                checkConsistency(n);
                i++;
            }
        }
    });

    it("résultat complémentaire varié lié indirectement", () => {
        createEnv();
        // vary LargeurBerge and compute Q on nub5
        prm5.Z1.setValues(50, 200, 10);
        prm5.Q.setCalculated();

        // link nub6.Q to nub5.CvQT
        prm6.Q.defineReference(nub5, "CvQT");

        // link other Nubs Q to nub6.Q
        for (const n of [ nub2, nub3, nub4 ]) {
            n.prms.Q.defineReference(nub6.section, "Q");
            // set every parameter to MINMAX / LISTE mode
            let i = 0;
            for (const p of n.parameterIterator) {
                if (p.symbol !== "Q") {
                    if (i % 2 === 0) {
                        p.setValues(1, 5, 0.5); // sets valueMode to MINMAX
                    } else {
                        p.setValues([ 1, 2, 3, 4, 5 ]); // sets valueMode to LISTE
                    }
                }
                checkConsistency(n);
                i++;
            }
        }
    });
});